Typescript, how to use a constructor of a class extending an abstract class as parameter

Viewed 524

I have an abstract class A. Then I have two classes, B and C, that extend that abstract class.

(Example code):

abstract class A {
    private name: string;

    abstract saluto(): void;

    constructor(name: string) {
        this.name = name;
    }
}

class B {
    saluto(): void {
        console.log(`Hello ${this.name}`);
    }

    constructor(name: string) {
        super(name);
    }
}

class C {
    saluto(): void {
        console.log(`Ciao ${this.name}`);
    }

    constructor(name: string) {
        super(name);
    }
}

I have a function that given the array const generators = [B, C] and a parameter returns me the instances of B and C created passing the parameter to the constctor.

For instance:

const generators = [B, C];

function generate(generators: typeof A[], name: string): A[] {
    return generators.map(gen => new gen(name)); // Here I have the error
}
const instances = generate(generators, 'Eugenio');

Here I have an error, because it says Cannot create an instance of an abstract class..

How can I solve this problem? There should be a way to tell to tsc that the generators are not A itself but non-abstract classes that extends it.

1 Answers

When you use typeof A, you basically create a type similar to abstract new(name: string) => A. The abstract keyword on a constructor (TS 4.2 abstract constructor signature) marks this class/constructor as not instantiable. So, if you want an instantiable class, you need to type it without the abstract new and just new (playground):

function generate(generators: (new(name: string) => A)[], name: string): A[] {
    return generators.map(gen => new gen(name)); // No error anymore
}

Edit: Automatically infer constructor signature.

To do this, you usually would use ConstructorParameters, but this is not possible since typeof A returns an abstract constructor, and ConstructorParameters only works for normal constructors. Thus we have to type that utility type ourselves using infer (playground):

type AbstractConstructorParameters<T> = T extends abstract new(...args: infer P) => any ? P : never;

type ParametersOfA = AbstractConstructorParameters<typeof A>;

function generate(generators: (new(...args: ParametersOfA) => A)[], ...args: ParametersOfA): A[] {
    return generators.map(gen => new gen(...args)); // No error anymore
}
Related