I am playing around with Typescript decorators and they seem to behave quite differently than what I'd expect when used alongside with class inheritance.
Suppose I have the following code:
class A {
@f()
propA;
}
class B extends A {
@f()
propB;
}
class C extends A {
@f()
propC;
}
function f() {
return (target, key) => {
if (!target.test) target.test = [];
target.test.push(key);
};
}
let b = new B();
let c = new C();
console.log(b['test'], c['test']);
Which outputs:
[ 'propA', 'propB', 'propC' ] [ 'propA', 'propB', 'propC' ]
Though I'd expect this:
[ 'propA', 'propB' ] [ 'propA', 'propC' ]
So, it seems that target.test
is shared between A, B and C. And my understanding of what is going on here is as follow:
new B()
triggers the instantiation of A first, which triggers the evaluation of f
for A. Since target.test
is undefined, it is initialized. f
is then evaluated for B, and since it extends A, A is instantiated first. So, at that time, target.test
(target
being B) references test
defined for A. So, we push propB
in it. At this point, things go as expected.test
, different than that defined for B. But the log proves me wrong.Can anyone explain to me why this is happening (1) and how would I implement f
such that A and B have separate test
properties ?
I guess you'd call that an "instance specific" decorator ?
TypeScript supports single inheritance and multilevel inheritance. We can not implement hybrid and multiple inheritances using TypeScript. The inheritance uses class-based inheritance and it can be implemented using extends keywords in typescript.
Decorators provide a way to add both annotations and a meta-programming syntax for class declarations and members. Decorators are a stage 2 proposal for JavaScript and are available as an experimental feature of TypeScript. NOTE Decorators are an experimental feature that may change in future releases.
Inheritance in TypeScriptTypescript uses class-based inheritance which is simply the syntactic sugar of prototypal inheritance. TypeScript supports only single inheritance and multilevel inheritance. In TypeScript, a class inherits another class using extends keyword.
target: Constructor function of the class if we used decorator on the static member, or prototype of the class if we used decorator on instance member. In our case it is firstMessage which is an instance member, so the target will refer to the prototype of the Greeter class. propertyKey: It is the name of the property.
Alright, so after spending a few hours playing around and searching the web, I got a working version. I don't understand why this is working, so please forgive the lack of explanation.
The key is to use Object.getOwnPropertyDescriptor(target, 'test') == null
instead of !target.test
for checking the presence the test
property.
If you use:
function f() {
return (target, key) => {
if (Object.getOwnPropertyDescriptor(target, 'test') == null) target.test = [];
target.test.push(key);
};
}
the console will show:
[ 'propB' ] [ 'propC' ]
Which is almost what I want. Now, the array is specific to each instance. But this means that 'propA'
is missing from the array, since it is defined in A. Hence we need to access the parent target and get the property from there. That took me a while to figure out, but you can get it with Object.getPrototypeOf(target)
.
The final solution is:
function f() {
return (target, key) => {
if (Object.getOwnPropertyDescriptor(target, 'test') == null) target.test = [];
target.test.push(key);
/*
* Since target is now specific to, append properties defined in parent.
*/
let parentTarget = Object.getPrototypeOf(target);
let parentData = parentTarget.test;
if (parentData) {
parentData.forEach(val => {
if (target.test.find(v => v == val) == null) target.test.push(val);
});
}
};
}
Which outputs
[ 'propB', 'propA' ] [ 'propC', 'propA' ]
May anyone that understands why this works while the above doesn't enlighten me.
I think it's becouse when class B is created prototype of A is copied with all it's custom properties (as references).
I used slightly modified solution, seams to solve more natural problem with duplicates if class C would not have any decorators.
Still not sure if this is the best way to handle such cases:
function foo(target, key) {
let
ctor = target.constructor;
if (!Object.getOwnPropertyDescriptor(ctor, "props")) {
if (ctor.props)
ctor.props = [...ctor.props];
else
ctor.props = [];
}
ctor.props.push(key);
}
abstract class A {
@foo
propA = 0;
}
class B extends A {
@foo
propB = 0;
}
class C extends A {
@foo
propC = 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With