torazaburo torazaburo - 3 months ago 22
TypeScript Question

TypeScript factory to create subclass

Consider the following code:

abstract class Foo { }
class Bar extends Foo { }

function factory(constructor: typeof Foo) {
return new constructor();
}

factory(Bar);


This doesn't compile:


error TS2511: Cannot create an instance of the abstract class 'Foo'.


I tried telling the factory I'd be instantiating a subclass:

function factory<U extends typeof Foo>(constructor: U) {


but that gives the same error. By the way, is this
U extends typeof Foo
doing what I hoped it would?

I wonder why TS does not realize that the parameter to
factory
might be a subclass of
Foo
which can be instantiated. If it complained about a call like
Factory(Foo)
then I'd understand, but...

How can I get this to compile?

Answer

You can use the constructor type to achieve this:

abstract class Foo { }

type FooConstructor = {
    new(): Foo;
}

class Bar extends Foo { }

function factory(constructor: FooConstructor) {
    return new constructor();
}

factory(Bar);

(code in playground)

You can have it with generics as well:

abstract class Foo { }

type FooConstructor<T extends Foo> = {
    new(): T;
}

class Bar extends Foo { }

function factory<T extends Foo>(constructor: FooConstructor<T>) {
    return new constructor();
}

factory(Bar);

(code in playground)

The difference being that if you do:

let o = factory(Bar);

Then in the first (non-generic) example the type of o will be Foo, while in the 2nd example if will be of type Bar.

This is used quite often in the lib.d.ts file, for example the ArrayConstructor:

interface ArrayConstructor {
    new (arrayLength?: number): any[];
    new <T>(arrayLength: number): T[];
    new <T>(...items: T[]): T[];
    (arrayLength?: number): any[];
    <T>(arrayLength: number): T[];
    <T>(...items: T[]): T[];
    isArray(arg: any): arg is Array<any>;
    readonly prototype: Array<any>;
}