Tristan
Tristan

Reputation: 6906

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

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[].

Upvotes: 19

Views: 5669

Answers (2)

Tristan
Tristan

Reputation: 6906

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();

Upvotes: 32

Sly_cardinal
Sly_cardinal

Reputation: 12993

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();

Upvotes: 0

Related Questions