Search code examples
node.jstypescriptoopinheritanceabstract-class

How to represent the Type of any Subclass of an Abstract class in Typescript?


Assuming

abstract class A {};
class B extends A {};
class C extends A {};

I want to accept as a parameter any Subclass of A.

function foo(Subclass: A){
   const obj = new Subclass();
}

This wont work. I get the error

This expression is not constructable. Type 'BaseController' has no construct signatures.

Then I tried

function foo(Subclass: typeof A){   
   const obj = new Subclass();
}

Now I get the error

Cannot create an instance of an abstract class.ts(2511)

Because it is assuming that I am passing A and not a subclass of A.

Is it achievable? How do I get this to work?


Solution

  • I think you should change the annotation to the following:

    function foo(subclass: new () => A) {
      const obj = new subclass();
    }
    

    Here, we are saying that subclass is newable (i.e., it's a constructor function you call with the new operator), takes no arguments, and constructs a value whose type is assignable to A. This should accept any concrete subclass of A as long as it has a no-arg constructor:

    foo(B); // okay
    foo(C); // okay
    

    It won't accept A itself, because an abstract class constructor is not considered newable:

    foo(A); // error, A is abstract
    

    Oh, as a note for people making example TypeScript code for Stack Overflow questions: Note that TypeScript's type system is structural. With the example definitions you gave, instances of A, B, and C are considered the same, empty type. And all objects are assignable to the empty type, so this also works:

    foo(Date); // okay also
    

    To prevent that, you should add properties to A, B, and/or C to distinguish them structurally, such as:

    abstract class A {
      a = "A"
    };
    class B extends A {
      b = "B"
    };
    class C extends A {
      c = "C"
    };
    

    Which will result in more expected behavior:

    foo(B); // okay
    foo(C); // okay
    foo(A); // error, A is abstract
    foo(Date); // error, 'a' is missing
    

    Okay, hope that helps; good luck!

    Playground link to code