Zephyr
Zephyr

Reputation: 7823

How to type Constructor as well as Constructor Argument in TypeScript?

Here is some set up code:

// This is from lib.d.ts
type ConstructorParameters<T extends new (...args: any[]) => any> 
      = T extends new (...args: infer P) => any ? P : never

// Represents a type with a constructor function
type Newable<TType> = {
    new(...params: any[]): TType;
};

// A sample class
class Foo {

    constructor(
        options: { bar: string }
    ) {
    }

    public bar() { }
}

Here is a generic class factory that I'd like to type but I'm failing at the moment:

// Error: 'T' refers only to a type, but is being used as a value here
declare function create<T>(kind: Newable<T>, options?: 
      ConstructorParameters<typeof T>[0]): T;

I want to use it like this:

var x = create(Foo, { bar2: 'ss' }); // This should fail because the constructor option is wrong
x.bar();

I understand the error, but does anyone know how to get this working?

Upvotes: 1

Views: 270

Answers (2)

Zephyr
Zephyr

Reputation: 7823

Alternative solution, closer to the original snippet:

type Newable<TType extends new(...args:any[]) => InstanceType<TType>> = {
    new(...params: any[]): InstanceType<TType>;
};


// A sample class
class Foo {

    constructor(
        options: { bar: string }
    ) {
    }

    public bar() { }
}

declare function create<T extends Newable<T>>(kind: T, options?: 
 ConstructorParameters<T>[0]): InstanceType<T>;

var x = create(Foo, { bar2: 'ss' }); // Error as expected
x.bar();

Upvotes: 0

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250366

There are several problems in your code the bigest one is that you can't do typeof T, you will need the type parameter to reresent the class not an instance of the class. If T is the class you can use InstanceType to get the instance type of the class. You can aslo use a conditional type to extarct the first prameter of the constructor. Also since you want the constructor to always have one parameter, I would not use the general new(...params: any[]): TType; better to create a custom type to repesent a constrcutor with just one argument type CtorWithOptions<TOpt extends object, T> = new (o: TOpt)=> T.

Putting it all together:

// A sample class
class Foo {

    constructor(
        options: { bar: string }
    ) {
    }

    public bar() { }
}

type CtorWithOptions<TOpt extends object, T> = new (o: TOpt)=> T
type OptionsFromConstructor<T> =  T extends CtorWithOptions<infer TOpt, any> ? TOpt : never;
declare function create<T extends CtorWithOptions<any, any>>(kind: T, options: OptionsFromConstructor<T>): InstanceType<T> {
    return new kind(options);

}

var x = create(Foo, { bar2: 'ss' }); // Fails
var x = create(Foo, { bar: 'ss' }); // OK
x.bar();

Upvotes: 1

Related Questions