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