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