Michel Bitter
Michel Bitter

Reputation: 95

Typescript: Override static factory method of parent Class in Child method

I'm running into some problems with dependency injection with Typescript. On every Class that I add a factory static method where all dependencies are set. I do this for testing purposes so that I'm still able to use the TDD approach.

Now I'm running into some problems with overriding the factory method of the parent class in a child class. Example:

interface DepsA {
  a: string
}

interface DepsB extends DepsA {
  b: Child1
}

class Parent {
  constructor(protected deps: DepsA | DepsB) {}

  public static factory<T extends Parent>() {
    return new this({a: 'This is a nice Dependency'}) as T
  }
}

class Child1 extends Parent {}

class Child2 extends Parent {
  public static factory() {
    return new this({a: 'This is a nice Dependency', b: Child1.factory()})
  }
}

const child1 = Child1.factory<Child1>()
const child2 = Child2.factory()

The error what I receive is:

[ts]
Class static side 'typeof Child2' incorrectly extends base class static side 'typeof Parent'.
  Types of property 'factory' are incompatible.
    Type '() => Child2' is not assignable to type '<T extends Parent>() => T'.
      Type 'Child2' is not assignable to type 'T'.

I know why I get the error, but have at this point no idea anymore how to fix it, otherwise than renaming the factory static method in Child2.

UPDATE: A Related bug report to this problem, that explains automatically why I use a Generic on the factory method is: #26298

Upvotes: 4

Views: 3794

Answers (1)

arvymetal
arvymetal

Reputation: 3244

First, there's a pre-defined conditional type called InstanceType which could help you to infer the class type from the static member:

public static factory<T extends typeof Parent>(this: T) {
    return new this({ a: 'This is a nice Dependency' }) as InstanceType<T>
}

Second, if you override a method, static or not, in a child class, it should have a compatible signature, including the generic stuff.

Consequently, your code block could look like this (see in Typescript Playground):

interface DepsA {
    a: string
}

interface DepsB extends DepsA {
    b: Child1
}

class Parent {
    constructor(public deps: DepsA | DepsB) {}

    public static factory<T extends typeof Parent>(this: T) {
        return new this({ a: 'This is a nice Dependency' }) as InstanceType<T>
    }
}

class Child1 extends Parent {}

class Child2 extends Parent {
    public static factory<T extends typeof Parent>(this: T) {
        return new this({a: 'This is a nice Dependency', b: Child1.factory()}) as InstanceType<T>
    }
}

const child1 = Child1.factory()  // Type: Child1
const child2 = Child2.factory()  // Type: Child2

From there, returning the proper deps type, rather than an union, would also be possible in non static members, using as this["deps"]. But you'd have to revamp a bit your code.

Hope it helps ;-)

Upvotes: 6

Related Questions