Maciej Kravchyk
Maciej Kravchyk

Reputation: 16597

TypeScript - get type of constructor of a pure class type

I want to use typeof operator to get the constructor type from class, but it doesn't seem to work when you want to use it on a dictionary of classes. E.g. typeof Modules[T] - instead of getting the type of Modules[T], the compiler thinks of (typeof Modules)[T]

class A { }

class B { }

abstract class Modules {
    public a!: A;
    public b!: B;
}

// Error: Type 'T' cannot be used to index type 'typeof Modules'
function addModule<T extends keyof Modules>(key: T, ModuleCtor: typeof Modules[T]) {
    let module = new ModuleCtor();
}

addModule('a', A);

// Works but the constructor arguments type is lost
function addModule2<T extends keyof Modules>(
    key: T,
    ModuleCtor: new (...args:any[]) => Modules[T]
) {
    let module = new ModuleCtor();
}

addModule2('a', A);

Playground url

Update

As Aleksey pointed out, typeof operator works only on values. Classes are actual values, but what I have here is pure types. I'm essentially looking for something that works like typeof Class (value) that would get the exact type of constructor (including its arguments) of a pure class type.

Upvotes: 4

Views: 4100

Answers (1)

Keith
Keith

Reputation: 24181

Your first problem is that you have to remember Typescript types are based on the Shape of an interface, there not Hard Types.

So in your example -> class A { } and class B { } are the same Type..

So the first thing we need to do is in your example is make A & B different somehow, so that Typescript can know the difference.

Otherwise you could do -> addModule('a', B); and I'm assuming you want to trap that.

eg.

class A { A?:string }
class B { B?:string }

In the above, class A & B types are now different, in a real application it's likely that your methods etc are different, so doing a dummy A?:string wouldn't normally be required.

The next problem is we need to define the shape of our constructor, if your constructor is empty, a simple example would be ->

type Construct<T> = new () => T;

After doing this our final result is ->

class A {
    A?: string
}

class B {
    B?: string
}

class Modules {
    a!: A;
    b!: B;
}

type Construct<T> = new () => T;

function addModule<T extends keyof Modules, C extends Construct<Modules[T]>>(key: T, ModuleCtor: C) {
  const x = new ModuleCtor()
}

var a = addModule('a', A)

//Argument of type 'typeof B' is not assignable to parameter of type 'Construct<A>'.
var a = addModule('a', B)

Playground

Upvotes: 3

Related Questions