Instantiating child class from a static method in base class, using TypeScript
Asked Answered
T

2

19

Being new to TypeScript, what is the best method to implement a static factory in a base class that instantiates the child class type. For instance, consider a findAll method in a base model class:

class BaseModel {
  static data: {}[];
  static findAll() {
    return this.data.map((x) => new this(x));
  }
  constructor(readonly attributes) {
  }
}

class Model extends BaseModel {
  static data = [{id: 1}, {id: 2}];
  constructor(attributes) {
    super(attributes);
  }
}

const a = Model.findAll();  // This is BaseModel[] not Model[]

This returns BaseModel[] rather than Model[].

Theone answered 15/7, 2017 at 23:54 Comment(0)
T
32

To answer my own question, this turns out to be a well known issue in TypeScript. The Github issue Polymorphic this for static methods has a long discussion. The solution is as follows:

export type StaticThis<T> = { new (): T };

export class Base {
    static create<T extends Base>(this: StaticThis<T>) {
        const that = new this();
        return that;
    }
    baseMethod() { }
}

export class Derived extends Base {
    derivedMethod() { }
}

// works
Base.create().baseMethod();
Derived.create().baseMethod();
// works too
Derived.create().derivedMethod();
// does not work (normal)
Base.create().derivedMethod();
Theone answered 23/7, 2017 at 6:44 Comment(5)
I can't describe how much time I spend to solve this. Thanks dudeDoggerel
@Theone I've just been on a tricky scenario since Base had at the same time a generic... and thinking about it... just for curiosity, what's the purpose of specifying that T extends Base ?Tatouay
I love this. It works when constructor doesn't take params, a more generic signature for create would be: static create<T extends Base>(this: { new (...args: any[]): T; })Tightwad
What is the this parameter being used for. It's a parameter set to the create static method, but it isn't being filled during Base.create() calls. How does TS know that this parameter is not necessary, and what if you need to pass a parameter into create static method?Mellman
It appears this gets elided. But how how would you extend the static method in the derived class?Mellman
O
0

You will need to pass the subtype constructor to the static function on the base type.

This is because the base class doesn't (and shouldn't) know anything about the subtypes to know which child constructor to use.

This is an example of how it might look - each subtype defines its own static findAll() method that calls the standard behaviour on the parent class, passing the data and constructor along for the parent to use:

class BaseModel {
    static data: {}[];

    static _findAll<T extends BaseModel>(data: any[], Type): T[] {
        return data.map((x) => new Type(x));
    }

    constructor(readonly attributes) {
    }
}

class Model extends BaseModel {
    static data = [{ id: 1 }, { id: 2 }];

    constructor(attributes) {
        super(attributes);
    }

    static findAll() {
        return BaseModel._findAll(this.data, this);
    }
}

const a = Model.findAll();
Onym answered 16/7, 2017 at 1:10 Comment(2)
there is better ways. See #34098523Straphanger
Thanks, I didn't know about the new this() ability from static functionsOnym

© 2022 - 2024 — McMap. All rights reserved.