Expanding an abstract TypeScript class and interface to include a return type mirroring itself

Consider this initial base interface and abstract class setup:

export interface IPersonViewModel {
    name: string;

    setName(value: string): IPersonViewModel;
}

export abstract class PersonViewModel implements IPersonViewModel {
    constructor(name: string) {
        this.name = name;
    }

    public name: string;

    public setName(value: string): IPersonViewModel {
        const vm = { ...this, name: value };
        return vm;
    }
}

Now, let's introduce a derived class and interface to the scenario:

export interface IEmployeeViewModel extends IPersonViewModel {
    role: string;
}

export class EmployeeViewModel extends PersonViewModel implements IEmployeeViewModel {
    constructor(role: string, name: string) {
        super(name);
        this.role = role;
    }
    public role: string;

    public setRole(value: string): IEmployeeViewModel {
        const vm = { ...this, role: value };
        return vm;
    }
}

The issue arises when using setName on an instance of IEmployeeViewModel as it returns the base type IPersonViewModel. Is there a solution involving generics or other methods to ensure that the return type of IEmployeeViewModel.setName is IEmployeeViewModel?

Answer №1

Utilizing generics is not necessary in this scenario. The key lies in utilizing the polymorphic this type. Simply use this as the return type in both the interface and the classes.

export interface IPersonViewModel {
    name: string;

    setName(value: string): this;
}
public setName(value: string): this {
...

By doing so, you are specifying to TypeScript that the return type of setName should align with the type of the original object.

const employee = new EmployeeViewModel('someRole', 'someName');
// renamed will have type EmployeeViewModel
const renamed = employee.setName('Bob');

Check out the Typescript Playground Link for reference.


If you find yourself interacting with an implementation of IPersonViewModel where you are unsure of the specific implementation, then using generics might be beneficial.

In such cases, the particular implementation is irrelevant as all IPersonViewModel objects possess a name and a setName.

const nameModifyFn = (object: IPersonViewModel): string => {
    return object.setName('new name').name;
}

However, by incorporating generics, you can ensure that the return type matches the original type.

const nameModifyFn = <T extends IPersonViewModel>(object: T): T => {
    return object.setName('new name');
}

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

Guide on importing absolute paths in a @nrwl/nx monorepo

I am currently working on a @nrwl/nx monorepo and I am looking to import folders within the project src using absolute paths. I attempted to specify the baseUrl but had no success. The only solution that did work was adding the path to the monorepo root ts ...

Error: The function to create deep copies of objects is not working properly due to TypeError: Object(...) is not a

Encountering a TypeError: Object(...) is not a function in the following situation: To set up the state of a component with a specific Article (to be fetched from the backend in componentDidMount), I am implementing this approach // ArticlePage.tsx import ...

Having trouble assigning the class property in Angular 5

Upon loading the page, a list of products is retrieved from an external JSON source. Each product in the list has a corresponding BUY button displayed alongside it, with the ID of the respective product assigned to the button. The intention is that when a ...

Determine the sum of exported identifiers based on ESLint rules

Currently, I am facing a requirement in my JavaScript/TypeScript monorepo to ensure that each library maintains a minimal amount of exported identifiers. Is there any existing eslint rule or package available that can keep track of the total number of exp ...

Leveraging Leaflet or any JavaScript library alongside Typescript and webpack for enhanced functionality

Important: Despite extensive searching, I have been unable to find a resolution to my issue. My current endeavor involves developing a map library through the extension of leaflet. However, it appears that I am encountering difficulties with utilizing js ...

Transform a group of objects in Typescript into a new object with a modified structure

Struggling to figure out how to modify the return value of reduce without resorting to clunky type assertions. Take this snippet for example: const list: Array<Record<string, string | number>> = [ { resourceName: "a", usage: ...

*ngif does not control the visibility of Angular Material Progresbar

In my application, the webpage needs to display a progress bar while fetching data from multiple APIs and constructing a PDF document. For this purpose, I am using the jsPDF library to create the PDF. Below is the implementation in my template: <div cl ...

Exporting the interface for the state of the redux store

After setting up a redux module, I have organized the following files: //state.tsx export default interface State { readonly user: any; readonly isLoggedIn: boolean; } //types.tsx export default { REQUEST: 'authentication/REQUEST', SUC ...

A guide to effectively utilizing a TypeScript cast in JSX/TSX components

When trying to cast TypeScript in a .tsx file, the compiler automatically interprets it as JSX. For example: (<HtmlInputElement> event.target).value You will receive an error message stating that: JSX element type 'HtmlInputElement' is ...

Can TypeScript verify the types of string aliases during typing?

Consider the following code snippet: type Firstname = string type Surname = string const firstname: Firstname = "John"; const surname:Surname = "Smith" function print(name: Firstname) { console.log(name) } /* * This should give a compile error * ...

Tips for how to retrieve an instance of a class or an error in Typescript

Currently, I am experimenting with TypeScript and a library called TypeORM while constructing custom repositories for my models. Within my models, specifically the Buyer model, there is a corresponding BuyerRepository featuring a method named createAndSav ...

Uploading images using Angular and PHP: A comprehensive guide

I am a beginner in Angular and I am having trouble uploading an image from Angular as I encounter 4 errors: 1) Error in the post method: Cannot find name 'formData'. Did you mean 'FormData'?ts(2552) 2) Error in the subscribe method: ...

Tips on using dual drop-down menus for sorting options

I am encountering a small issue with my two filters. When I choose the values IN and ENCODE, all the values are displayed correctly... https://i.sstatic.net/Uoido.png However, the problem arises when I click on OUT, as the status is not displayed correc ...

When declaring an array of numbers in sequelize-typescript, it triggers a TypeScript error

In my application, I am working with PostgreSQL, Sequelize, Sequelize-TypeScript, and TypeScript. I have a need for a table called Role where each role has multiple permissions of type integer. I'm following the guidelines provided in the sequelize-ty ...

Attributes could potentially be null

I created a small Vue functional component that receives a prop from its parent: export default defineComponent({ name: 'ExpandedMovieInformation', props: { movie: { type: Object as PropType<Movie>, }, }, setup(props, ...

error TS2559: The type 'BookInterface[]' does not share any properties with the type 'BookInterface'

Hello, I am currently working on a project using Angular 7 and encountering the error TS2559: Type 'BookInterface[]' has no properties in common with type 'BookInterface'. Despite making changes to the code, the issue persists. Below is ...

How to handle the results of Promise.all() in a typescript project

When working with TypeScript, I encountered an issue while trying to assign the results of Promise.all(). It seems that Promise.all() changes the return type to number | <actual type>. Even when attempting to handle this inside a then() statement... ...

How can I utilize Pinia and TypeScript to set the State using an Action?

I have a Pinia + TypeScript store named user.ts with the following structure: import { User } from 'firebase/auth'; import { defineStore } from 'pinia'; export const useUserStore = defineStore('user', { state: () => ( ...

Using interfaces for typecasting in TypeScript

Consider the code snippet below: let x = { name: 'John', age: 30 } interface Employee { name:string, } let y:Employee = <Employee>x; console.log(y); //y still have the age property, why What is the reason for TypeScript ov ...

Can someone explain the inner workings of the Typescript property decorator?

I was recently exploring Typescript property decorators, and I encountered some unexpected behavior in the following code: function dec(hasRole: boolean) { return function (target: any, propertyName: string) { let val = target[propertyName]; ...