I want to validate the value of a property when it is set, by adding a decorator in the class definition.
function Test() {
return (target: object, key: string) => {
let val = '';
Object.defineProperty(target, key, {
get: () => val,
set: (v: string) => {
if(/.*\s.*/.test(v)) throw new Error(`Id '${v}' must not contain any whitespace!`)
val = v;
},
enumerable: true
});
}
}
class FooClazz {
@Test()
id: string;
constructor(id: string) {
this.id = id;
}
}
const test = new FooClazz("myId");
console.log(Object.keys(test)) // -> [];
The setter validator works, but when I try to log out the object or its keys, id
does not show up. Even though I set enumerable
to true
.
What am I doing wrong?
The problem is Test
decorator decorates the prototype
property of the FooClazz
class. But you're trying to get own keys of FooClazz
's instance. While id
property exists on it's prototype chain:
console.log(Object.keys(test.__proto__)) // -> ["id"];
Yet another problem is Test
decorator runs only once (when decorates FooClazz
's prototype) and val
variable is 'shared' among the instances (though it may be intentional?).
const test = new FooClazz("myId");
const anotherTest = new FooClazz("anotherId");
console.log(test.id); // -> "anotherId"
console.log(anotherTest.id); // -> "anotherId"
You may use a trick with redefining the key on the instance itself on the first set:
function Test(target: any, key: string) {
Object.defineProperty(target, key, {
get: () => '',
set: function(v: string) {
var val = '';
Object.defineProperty(this, key, {
get: () => val,
set: (v: string) => {
if(/.*\s.*/.test(v)) throw new Error(`Id '${v}' must not contain any whitespace!`)
val = v;
},
enumerable: true,
});
this[key] = v;
},
});
}
class FooClazz {
@Test
id: string;
constructor(id: string) {
this.id = id;
}
}