Reputation: 95
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
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