user8607541
user8607541

Reputation:

Extending abstract Typescript class and interface with return type of itself

Consider the following base interface and abstract class:

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 consider the following derived class and interface:

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;
    }
}

If setName is used on an instance of IEmployeeViewModel the return type is IPersonViewModel. Is there any way to use generics or some other methodology such that the return type of the base interface function is the same as the implemented interface i.e. IEmployeeViewModel.setName returns IEmployeeViewModel?

Upvotes: 0

Views: 1333

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42208

You don't need generics. All that you need is the polymorhic this type. 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 {
...

Now you are telling TypeScript that the returned type of setName should match the type of the original object.

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

Typescript Playground Link


You might use generics if you were interacting with an implementation of IPersonViewModel in a situation where you didn't know what specific implementation you were dealing with.

This sort of function is fine, because the implementation doesn't matter. All IPersonViewModel objects have a name and a setName.

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

But here you could use a generic so that the return type matches the original type.

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

Upvotes: 1

Related Questions