I have a lot of similar classes that I'd like to initialize with the following syntax:
class A {
b: number = 1
constructor(initializer?: Partial<A>) {
Object.assign(this, initializer)
}
}
new A({b: 2})
I think that being able to get initialized by such means is a generic behaviour and so I'd like to isolate this logic to avoid repeating myself in douzens of files. I tried this:
class Initializable<T> {
constructor(initializer?: Partial<T>) {
Object.assign(this, initializer)
}
}
class A extends Initializable<A> {
b: number = 1
}
new A({b: 2})
This compiles but doesn't work because the implicit super()
goes first so b
gets 2
as wanted but then gets 1
.
Does TypeScript offers a type-safe solution to get this behaviour in all my classes?
There is no simple way to run something from the base class after the derived class constructor has finished. The only solution I can see (and I invite others to come up with a better one :) ) is to use a function which augments what will become the A
class instead of using a base class. Basically the mixin approach to adding functionality.
function initializable<T extends new() => any>(cls: T) : {
new (data?: Partial<InstanceType<T>>) : InstanceType<T> // add the constructor
} & Pick<T, keyof T> /* add statics back */ {
return class extends (cls as any) {
constructor(data?: Partial<InstanceType<T>>){
super();
if(data) {
Object.assign(this, data);
}
}
} as any
}
const A = initializable(class {
b: number = 2;
static staticMethod() { }
method() {}
});
type A = InstanceType<typeof A> // Optionally define the type for A to have the same effect as the class
var a = new A({b:1});
console.log(a.b) // output 1
a.method();
A.staticMethod();
a = new A();
console.log(a.b) // output 2
var aErr = new A({b:"1"}); //error
Note Mix-in usually are not allowed not to change the constructor arguments and this is why we have to massage the types a little but it works out.