Yovar
Yovar

Reputation: 550

generic class partial initializer

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?

Upvotes: 2

Views: 198

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249476

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.

Upvotes: 1

Related Questions