Victor Ferreira
Victor Ferreira

Reputation: 6449

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?

Upvotes: 2

Views: 1554

Answers (2)

jcalz
jcalz

Reputation: 328292

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

Upvotes: 6

Leon
Leon

Reputation: 12481

For this to work you need to refer to the class by it's constructor reference.

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

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

In this case you can pass both B and C to foo since they have constructors. A will not be accepted since there is no constructor on an abstract class.

Upvotes: 1

Related Questions