0

I want to create an instance of T in a generic class in Typescript. The following code is something I thought would work, but unfortunately it does not.

type Constructor<T> = { new(...args: any[]): T };
class Bar { }
class Foo<T extends Constructor<T>> {

    constructor( /* some parameters here, but NOT a type creator! */ ) {}

    create(): T {
        return new T();
    }
}
const bar: Bar = new Foo<Bar>().create();

Well there are some other questions/answers here on SO, but all use some kind of type creator that needs to be passed to the generic class/function, like so:

function create<T>(creator: new() => T): T {
    return new creator();
}
const bar: Bar = create<Bar>(Bar);

But my ctor needs to not have something like that. The create function should always stay parameter-less. Is there a way to make this work?

1
  • Why must create be parameterless? Can you pass the creator parameter instead to the constructor of the Foo class? You have to pass the constructor of your class T into Foo somehow, one way or another, or this won't work. Try considering how you would do it in plain JS, then add types afterward. Commented Feb 21, 2022 at 22:56

2 Answers 2

2

In short: No it's not possible without a reference to a constructible runtime value.

The reason is that TypeScript types only exist at compile time (not at runtime) and can't be used as values. In your example, T is only a type (not a runtime value). This is the JavaScript that your TypeScript program compilation would produce:

class Bar {}

class Foo {
  constructor() { }
  create() {
    return new T();
  }
}

const bar = new Foo().create();
//                    ^^^^^^^^
// Exception is thrown: ReferenceError: T is not defined

You can see that T is a reference to a runtime value that doesn't exist, and so it throws a ReferenceError exception when the create method is invoked.


Instead, you can accept a constructible object (and its arguments) and return a constructed instance:

TS Playground

type Constructible<
  Params extends readonly any[] = any[],
  T = any,
> = new (...params: Params) => T;

class Foo {
  static create <T extends Constructible>(
    constructible: T,
    ...params: ConstructorParameters<T>
  ): InstanceType<T> {
    return new constructible(...params);
  }
}

class Bar {}

class Baz {
  constructor (readonly param1: string, readonly param2: number) {}
}

const bar = Foo.create(Bar);
const baz = Foo.create(Baz, '2', 2);

Sign up to request clarification or add additional context in comments.

Comments

1

You can't create any instance of a class without a runtime reference to that class constructor.

You could pass your class to be created as a value to the creator's constructor, and then generically capture that type's value.

I would make you Constructor non-generic, just to use it as a constraint, and let typescript infer the rest.

type Constructor = { new (...args: any[]): any };

For example:

class Foo<T extends Constructor> {
    constructor(private ctor: T) {}

    create(): InstanceType<T> {
        return new this.ctor();
    }
}

Which lets you do this:

class Bar { }

const foo = new Foo(Bar)
const bar1: Bar = foo.create(); // no parameters
const bar2: Bar = foo.create(); // no parameters

Playground


For bonus points, you can pick up the constructor parameters and make your create() method take them:

    create(...ctorArgs: ConstructorParameters<T>): InstanceType<T> {
        return new this.ctor(...ctorArgs);
    }

Which let's you do:

class Bar {
    constructor(a: number, b: string) {}
}

const foo = new Foo(Bar)
const bar1: Bar = foo.create(1, 'a'); // no parameters
const bar2: Bar = foo.create(2, 'b'); // no parameters

Playground

Or you could put this in the creators constructor instead?

class Foo<T extends Constructor> {
    ctorArgs: ConstructorParameters<T>

    constructor(private ctor: T, ...ctorArgs: ConstructorParameters<T>) {
        this.ctorArgs = ctorArgs
    }

    create(): InstanceType<T> {
        return new this.ctor(...this.ctorArgs);
    }
}

class Bar {
    constructor(a: number, b: string) {}
}

const foo = new Foo(Bar, 1, 'a')
const bar1: Bar = foo.create(); // no parameters
const bar2: Bar = foo.create(); // no parameters

Playground

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.