1

Sorry if this has been covered already, but my vocabulary is failing me :(

I'm trying to write a "collection" class, where at instantiation - I give it a "Model" class definition. The collection instance needs to be able to stamp out instances of "Model", but also needs to be able to call static methods on that class.

type Ctor<T> = new (...args: any[]) => T

class Model<T> {
  foo: string
  bar: string
  static myMethod() {}
}

class Extension extends Model<Extension> {
  foobar() {}
}

Extension.myMethod() // works
const e = new Extension()

e.foobar() // works

class Collection<T> {
  public model: T

  constructor(model: T) {
    this.model = model
  }

  getModelInstance() {
    // clearly this is problematic
    const Model = this.model as unknown as Ctor<T> 
    return new Model()
  }
}

Now the problem becomes when I try to use it:

const c = new Collection(Extension)
c.model.myMethod() // works

const m = c.getModelInstance()
m.foobar() // works at runtime, TS compiler says 'Property 'foobar' does not exist on type 'typeof Extension'.'

So I can redefine my constructor/instance definitions a bit:

class Collection<T> {
  public model: Ctor<T>

  constructor(model: Ctor<T>) {
    this.model = model
  }

  getModelInstance() {
    const Model = this.model
    return new Model()
  }
}
const c = new Collection(Extension)
c.model.myMethod() // Property 'myMethod' does not exist on type 'Ctor<Extension>'.

const m = c.getModelInstance()
m.foobar() // works

However this doesn't work because Ctor drops my static "context".

Is this possible? It seems like either form I lose type information.

1 Answer 1

2

I am fairly sure this will work for you. The relevant changes are to the MyCollection class and the use of InstanceType. Here it is in the playground.

class Model<T> {
    foo: string;
    bar: string;
    static myStaticMethod() { }
}

class Extension extends Model<Extension> {
    myInstanceMethod() { }
}

Extension.myStaticMethod();
const e = new Extension();
e.myInstanceMethod();

class MyCollection<TConstructor extends new () => InstanceType<TConstructor>> {
    public modelConstructor: TConstructor

    constructor(modelConstructor: TConstructor) {
        this.modelConstructor = modelConstructor
    }

    getModelInstance() {
        return new this.modelConstructor();
    }
}

const collection = new MyCollection(Extension)
collection.modelConstructor.myStaticMethod();

const model = collection.getModelInstance();
model.myInstanceMethod();
Sign up to request clarification or add additional context in comments.

3 Comments

🙌🙌🙌 thank you very much! I missed the InstanceType stuff in the docs. I wish there were an API reference.
One problem I did have was that inside of getModelInstance(), we do not have access to any of the type defined on the base Model. Makes sense, the concrete types of Extension would not be available until you have an instance of Collection. I was able to get around this by defining getModelInstance method like so: const M = this.modelConstructor as unknown as typeof Model return new M() as InstanceType<T> Dirty, but it works.
@JonJaques Brute force for the win!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.