Defining Typescript function return type using object properties

I've come across Typescript's conditional : T extends U ? X : Y syntax and found it to be quite powerful. However, I haven't been able to figure out a way to specify the return type based on an attribute of a class or within the function itself. Consider this simple class:

class X {
    members: number[] = [];
    returnRoman = false;

    first(): number|string {
        if (!this.returnRoman) {
            return members[0];
        } else {
            return numberToRomanNumeral(members[0]);
        }
    }
}

Is there a way to constrain the return type to just number or string for better type-checking? The issue arises from a scenario like this:

class Base {
    x: number = 0;
}

class Sub extends Base { 
    y: number = 0;
}

class FilterIterator {
    members: Base[] = [];
    filterClass: typeof Base = undefined;

    class first(): Base {
        for (const m of this.members) {
            if (this.filterClass !== undefined && !(m instanceof this.filterClass)) {
                continue;
            }
            return m;
        }
    }
}

const fi = FilterIterator();
fi.filterClass = Sub
fi.members = [new Sub(), new Sub()];

const s = fi.first();
if (s.y > 0) {
   console.log('s is moving up in the world!');
}

The problem here is that fi.first() is expected to return Base, causing errors when attempting to access y which is not part of the Base class. One workaround could be:

const s = fi.first() as Sub;

However, if we had a situation like this instead:

class FilterIterator extends Base {
    ...
    filterClass = FilterIterator;  
}

then you end up with code like:

const firstOfFirstOfFirst = (((fi.first() as FilterIterator).first() 
    as FilterIterator).first() as FilterIterator);

This becomes even more complicated when dealing with generators that can return other generators, as declaring types within for...of loops isn't currently supported. It also shifts the responsibility of determining the returned value to the consuming software rather than having FilterIterator or Base handle it.

If you have any solutions or improvements, I'd love to hear them.

Answer №1

It may be more challenging to properly type your simplified example because the instance variable returnRoman can potentially change its value multiple times within the same instance. In order to update it, you would need to use a setter and utilize the syntax

asserts this is this & SomeType
.

Your specific use case might encounter similar difficulties when needing to convert a type after it has already been set, but we can overcome these obstacles by passing the filterClass as an argument in the constructor. This approach ensures that the type of the filterClass can always be inferred correctly for an instance right from the moment it is created. Thus, making it a straightforward scenario for utilizing generic classes.

type Constructor<T> = new (...args: any[]) => T;

class FilterIterator<T> {
    members: (T | Base)[] = [];
    private filterClass: Constructor<T>;

    constructor( filterClass: Constructor<T> ) {
        this.filterClass = filterClass;
    }

    first(): T {
        for (const m of this.members) {
            if (m instanceof this.filterClass) {
                return m;
            }
        }
        // It's necessary to either throw an error or extend the return type to include undefined
        throw new Error("no match found");
    }
}

The instanceof operator serves as an automatic type guard in TypeScript. Therefore, when you reach the line with return m, TypeScript understands that m is definitely of the same type T as your filterClass.

It seems like you are aiming to achieve something slightly different, where the filter acts as a custom check rather than a class constructor. However, without sufficient information, typing it accurately becomes challenging. You could define your filter as a user-defined type guard.

type Filter<T> = (value: any) => value is T;

class CustomFilterIterator<T> {
    members: any[] = [];
    private filter: Filter<T>;

    constructor( filter: Filter<T> ) {
        this.filter = filter;
    }

    first(): T | undefined {
        for (const m of this.members) {
            if (this.filter(m)) {
                return m;
            }
        }
        return;
    }
}

const isNumber = (value: any): value is number => {
    return typeof value === "number"
}

const numberFilter = new CustomFilterIterator(isNumber);
numberFilter.members = [55, "55", "hello", 78.3];
numberFilter.first()?.toExponential();

Typescript Playground Link

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Typescript Error: TS2339: The property 'faillogout' is not found within the type '{ failed(): void; onSubmit(): void; }'

I encountered an issue with my Vue.js app using TypeScript. The error message I'm getting is: Property 'faillogout' does not exist on type '{ failed(): void; onSubmit(): void; }'. 101 | failed () { This snippet shows the s ...

Copying the position and rotation of a mesh from one world to another in ThreeJS

In my setup, I currently have 3 meshes named cube(a), tempSphere(b), and m_mesh(c). Mesh a and b are grouped together with a as the parent mesh. I have managed to update the location, rotation, and position of the group, so that meshes a and b automatical ...

When trying to create a Map or OrderedMap in TypeScript with Immutable JS, you may encounter the error message "No overload matches this call."

In my project, I am utilizing Typescript version 3.8.3 alongside <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="395054544c4d585b555c790d17091709144b5a17">[email protected]</a>. An issue arises when attempting to ...

What could be causing the inner array typescript to be inaccessible in an Angular 5 application?

Below are the JSON definitions that I am working with: export class Company { name: string; trips : Trip[] = []; } export class Trip{ id: number; name: string; } Within the component, there is a method that contains the ...

Firebase Angular encountering issues with AngularFirestoreModule

I have encountered a challenge with Firebase authentication in my Angular applications. Due to updated read and write rules that require auth!=null, I successfully implemented Firebase authentication in one of my apps using Angular 13. Now, I am trying to ...

Angular component launches Bootstrap modal in the background

I have a modal that opens from the table within the material tab Below is the HTML code for the Modal: <div bsModal #createOrEditModal="bs-modal" class="modal fade" tabindex="-1" role="dialog" aria- ...

React's componentDidUpdate being triggered before prop change occurs

I am working with the CryptoHistoricGraph component in my app.js file. I have passed this.state.coinPrices as a prop for this element. import React from 'react'; import axios from 'axios'; import CryptoSelect from './components/cry ...

Best practice for implementing FieldValue.serverTimestamp() in a Typescript project

When using Tyepscript, I attempted to set the createdAt field in one of my Firebase functions with FieldValue.serverTimestamp(), but encountered an error message: Cannot read properties of undefined (reading 'serverTimestamp'){"severity&qu ...

Localization & Internationalization in Angular 6 CLI for enhancing multi-language functionality

Our Application is built on Angular 6 and we are looking to incorporate multilingual support. Please advise on how we can enable localization and internationalization in Angular 6. This pertains specifically to the Angular 6 version. ...

Utilizing Dynamic URLs in Kendo for Data Binding or Attributes on List Elements

I am facing an issue with routing two menu items conditionally using Kendo Jquery/MVVM. Originally, I had set the value in a data-href tag and it was displaying the URL correctly. However, I now need to dynamically load a URL based on certain conditions. T ...

Reduce the size of log messages in cypress

I am looking to shorten the cypress messages to a more concise string, for instance: Cypress log Transform to: -assert expected #buy-price-field to have value 17,169.00. Is there a way to achieve this? I have searched through the documentation but hav ...

What is the best way to pass a URL as a prop in Next.js without encountering the issue of it being undefined

Within my file (handlers.ts), I have a function designed to post data to a dynamic API route while utilizing the app router. This function requires values and a URL for fetching. However, when I pass the URL as a prop like this: http://localhost:3000/unde ...

Creating a progressive prototype chain in TypeScript: A step-by-step guide

With JavaScript, it is possible to create a "derived class" whose "base class" is dynamic using code like the following: function NewBaseClass(sF) { function DynamicBaseClass(iF) { this.instanceField = iF; } // EDIT: oops, this is not really static i ...

Implementing a defined string constant with a specific type in Typescript

Consider the following predefined object: const obj = { ONE: 'ONE', TWO: 'TWO' }; If I attempt to assign the constant obj.ONE to a new type like this: type TOne = obj.ONE An error message is displayed stating: Cannot find names ...

`Cannot retrieve object`

this.deleteValue = { LanguageId : '', LanguageName : '', LongName : '', CreatedBy : '', UpdatedBy : '', CreatedDate : '', ...

The issue lies in attempting to assign an 'Observable<number[]>' to a parameter expecting an 'Observable<ProjectObject[]>'. This obstacle must be overcome in order to successfully create a mock service

I am currently working on setting up a mock service for unit testing, but I am facing an issue where the observable is not returning the expected fake value. Can someone please assist me in resolving this problem and also explain what might be wrong with m ...

Errors of various kinds are occurring in Webpack during the npm install process

Whenever I execute npm install or npm ci, it triggers a webpack build that generates a plethora of incorrect errors. These errors allude to missing dependencies that are actually present. Interestingly, running npm ci seems to produce more errors than npm ...

What's causing the discrepancy in the parameters?

I have an issue with the following method: import { getCookie } from 'cookies-next'; export const getAccessTokenFromCookies = ( req?: NonNullable<Parameters<typeof getCookie>[1]>['req'], res?: NonNullable<Parameters& ...

To form a new array object, merge two separate arrays into one

https://stackblitz.com/edit/angular-enctgg-dvagm3 Issue: Attempting to update the hours from arr2 to arr1 and create the desired output below. Trying to achieve this using map function, but unsure how to navigate through nested arrays. Note: Array1 contai ...

Who is responsible for the addition of this wrapper to my code?

Issue with Sourcemaps in Angular 2 TypeScript App Currently, I am working on an Angular 2 app using TypeScript, and deploying it with the help of SystemJS and Gulp. The problem arises when I try to incorporate sourcemaps. When I use inline sourcemaps, eve ...