I'm working on a generic Typescript interface where a factory class is instantiated with a particular class and has methods responsible for creating various instances of that class. My ideal type interface, which I cannot seem to achieve, is as follows:
class BaseModel { /* impl */ }
class Foo extends BaseModel { /* impl */ }
class Factory<T extends BaseModel> { /* impl */ }
let factory : Factory<Foo> = new Factory(Foo)
let fooInstance = factory.build() // returns type: `Foo`
However I cannot figure out how to get a declaration of Factory<T>
to achieve this without either compromising the return type or maintaining a parallel constructor type definition.
If I rely on nothing but the types given by the classes I've declared, the return type of the build function is always BaseClass
, plus I have to make the generic Factory<typeof Foo>
instead of Factory<Foo>
:
class BaseModel {
static classMethod() : string {
return 'i am class method'
}
hello() {
return 'hello world'
}
}
class Foo extends BaseModel {}
class Factory<T extends typeof BaseModel> {
private _modelClass : T
constructor(modelClass : T) {
this._modelClass = modelClass
}
build() {
return new this._modelClass()
}
echoClassMethod() {
console.log(this._modelClass.classMethod())
}
}
let factory : Factory<typeof Foo> = new Factory(Foo)
let fooInstance = factory.build() // Returns type `BaseClass` instead of `Foo`
But if I want the factory type interface to work correctly, I have to maintain a parallel type definition that mirrors typeof BaseModel
, but as a constructor function instead:
class BaseModel {
static classMethod() : string {
return 'i am class method'
}
hello() {
return 'hello world'
}
}
class Foo extends BaseModel {}
// Have to maintain a separate type that
// mirrors the class interface of my target class
type BaseModelConstructor<T extends BaseModel> = {
new(...args: any[]) : T
classMethod() : string
}
class Factory<T extends BaseModel> {
private _modelClass : BaseModelConstructor<T>
constructor(modelClass : BaseModelConstructor<T>) {
this._modelClass = modelClass
}
build() {
return new this._modelClass()
}
echoClassMethod() {
console.log(this._modelClass.classMethod())
}
}
let factory : Factory<Foo> = new Factory(Foo)
let fooInstance = factory.build() // Correctly returns type `Foo`
There has to be a better way to convert a typeof X
into an X
or vice versa?
Assuming X
is the both the name of a constructor value and the name of the
instance type (which is what happens when you declare class X {...}
):
UPDATE:
As of TypeScript 2.8, there is a predefined type function called InstanceType<>
which takes the type of a class constructor and evaluates to the type of its instance, using conditional type inference instead of a lookup. So you can now get from typeof X
to X
by using InstanceType<typeof X>
. But (typeof X)['prototype']
below still works.
To get from typeof X
to X
, you can usually lookup the prototype
property of the typeof X
constructor type. For example, in the following I've annotated the return type of build()
to be T['prototype']
:
class Factory<T extends typeof BaseModel> {
private _modelClass : T
constructor(modelClass : T) {
this._modelClass = modelClass
}
// note the declared return type
build(): T['prototype'] {
return new this._modelClass()
}
echoClassMethod() {
console.log(this._modelClass.classMethod())
}
}
And then the following works:
let factory = new Factory(Foo)
let fooInstance = factory.build() // Foo, as desired.
Does that help? Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With