Search code examples
typescriptabstract-classtypescript-typingstypescript-genericstypescript-class

Assigning a generic type for derived classes from an abstract class with type variables in TypeScript


I have an abstract class Base that accepts a type variable <T> to be used within the class. Then I have many derived classes that explicitly define the type, such as class Derived extends Base<string> {...}

I want to have a variable (or array of variables) who's type can be any of those derived classes, no matter what <T> is. Then I want to be able to use that variable to create new instances of these Derived classes.

Here's some code with what I have tried. From there, I am lost.

TypeScript Playground link

abstract class Base<T> {
    abstract value: T;
}

class Derived extends Base<string> {
    value = 'Hello world!';
}

class SecondDerived extends Base<number> {
    value = 1234;
}

// This has type (typeof Derived | typeof SecondDerived)
let classes_A = [Derived, SecondDerived];

// This obviously works too, but with many derived classes can get long and tedious
let classes_B: (typeof Derived | typeof SecondDerived)[] = [];
classes_B.push(Derived);
classes_B.push(SecondDerived);

// This does NOT work
let classes_C: Base<any>[] = [];
classes_C.push(Derived); // "typeof Derived is not assignable to type Base<any>"

// This does NOT work
let classes_D: Base<unknown>[] = [];
classes_D.push(Derived); // "typeof Derived is not assignable to type Base<unknown>"

// This does NOT work
let classes_E: Base<string>[] = [];
classes_E.push(Derived); // "typeof Derived is not assignable to type Base<string>"

// This does NOT work
let classes_F: (typeof Base)[] = [];
classes_F.push(Derived); // "typeof Derived is not assignable to typeof Base"

Solution

  • My suggestion is this:

    let classes: Array<new (...args: any) => Base<any>> = [];
    classes.push(Derived); // okay
    classes.push(SecondDerived); // okay
    

    The element type of the array should be "a constructor of any subtype of Base<T> for any T". To say "a constructor of X" as a type, you use a newable signature, like new () => X. Note that the signature specifies what types and number arguments the constructor expects; if you don't care, then you can use a rest argument of type any or any[], like new (...args: any) => X.

    Since the type you're constructing is any subtype of Base<T> for any T and you presumably don't need to keep track of which, then Base<any> is probably good enough. (If not, then please elaborate on a use case for which this doesn't work).

    Okay, hope that helps; good luck!

    Playground link to code