Search code examples
node.jstypescripttypescript-genericstypescript-class

Typescript -- instantiate child from a static method in base class, with args and referencing other static method


I am trying to instantiate a child class from a static method in a base class. I want to type my base class correctly rather than using any as the return type on all my static methods. I tried the solution here but it doesn't work for a static method that references other static methods, or accepts arguments. How can I correctly inherit from a base class in typescript, and still reference other methods and accept arguments?

class BaseClass {
  id: string;

  [key: string]: unknown;

  static getName() {
    return this.name.toUpperCase()
  }

  static async find<T extends BaseClass>(this: new (...args: any[]) => T, id: string)
  : Promise<T> {
    const tableName = this.getName();

    const result: GetResult = db.find(tableName, id);

    return new this(result);
  }
}

class Child extends BaseClass {
  name: string;

  static findOne(id: string): Promise<Child> {
    return this.find(id);
  }
}

Child.find('abcd');

This causes two different errors

  1. Property 'getName' does not exist on type 'new (...args: any[]) => T'. (in the find method)
  2. Type 'BaseModel' is missing the following properties from type 'Child': name. (in the return type for findOne method)

Solution

  • In find method of the base class you should specify that it expects child class to implement static getName method, like this:

    static async find<T>(this: { new (arg: GetResult): T } & typeof BaseClass, id: string): Promise<T>
    

    Particularly { new (arg: GetResult): T } brings you constructor and typeof BaseClass brings you static members.

    I mocked some missing parts and it typechecks.

    type GetResult = string;
    
    const db = {
        find: (a: string, b: string) => "bar",
    }
    
    class BaseClass {
      id: string = "bzzzz";
    
      [key: string]: unknown;
    
      static getName() {
        return 'NAME'
      }
    
      static async find<T>(this: { new (arg: GetResult): T } & typeof BaseClass, id: string)
      : Promise<T> {
        const tableName = this.getName();
    
        const result: GetResult = db.find(tableName, id);
    
        return new this(result);
      }
    }
    
    class Child extends BaseClass {
      name: string = "Child";
    
      static findOne(id: string): Promise<Child> {
        return this.find(id);
      }
    }
    
    Child.find('abcd');