I'm using a Property Decorator to create an Observable with static getter/setter for every property.
In the end you can use the decorator in this way
class Test {
@ObservableProperty(DEFAULT_CATS)
cats: number;
@ObservableProperty(DEFAULT_PIGS)
pigs: number;
}
The actual code for the decorator is
export function ObservableProperty(defaultValue = null): any {
return (target, key, descriptor) => {
const accessor = `${key}$`;
target[accessor] = new BehaviorSubject(defaultValue);
return Object.assign({}, descriptor, {
get: function() {
return this[accessor].getValue();
},
set: function(value: any) {
this[accessor].next(value);
},
});
};
}
Now everything works fine with one instance of the Test
component.
But with two instances this test actually fails.
fdescribe('ObservableProperty Decorator', () => {
let test: Test;
let doppleganger: Test;
beforeEach(() => {
test = new Test();
doppleganger = new Test();
});
it('should create different observables for each props', () => {
expect(test['cats$'] === doppleganger['cats$']).toBe(false);
});
})
Because the decorator works on the prototype of the component instances the created variables are exactly the same one.
How can I get around this issue?
If it cannot be done with a decorator what's an elegant alternative way?
I'm going to answer the question with the solution i found after one day of thinking.
First of all the main problem for which i couldn't access the instance was in the use of the arrow function in the definition of the decorator. So i changed:
return (target, key, descriptor) => {
in
return function (target, key) {
This way i could access the instance from inside the getter/setter using this
.
Then i had to find a good spot to initialize the BehaviourSubject. Doing it in getter or setter of the main property wasn't going to work (i want to access this.cats$
without accessing first this.cats
).
So i solved with a new getter for cats$
. That stores the variable in a secret property and creates it if it doesn't exist.
Here's the final code!
export function ObservableProperty(defaultValue = null): any {
return function (target, key) {
const accessor = `${key}$`;
const secret = `_${key}$`;
Object.defineProperty(target, accessor, {
get: function () {
if (this[secret]) {
return this[secret];
}
this[secret] = new BehaviorSubject(defaultValue);
return this[secret];
},
set: function() {
throw new Error('You cannot set this property in the Component if you use @ObservableProperty');
},
});
Object.defineProperty(target, key, {
get: function () {
return this[accessor].getValue();
},
set: function (value: any) {
this[accessor].next(value);
},
});
};
}