I have a class in Typescript, in which I need to mark a number of methods as to whether they can be run on the server, the client, or both. I was going to use a decorator, so I would be able to mark the relevant methods with:
class Game {
@serverAction
getRandomNumber(): number { return 4; }
@clientAction
pass() { }
}
etc. Wrapping the value in the decorator gets me some of the way, but I still need to be able to identify from outside the method (and potentially outside the class) which decorators are given to method.
How can I make those decorators accessible?
My first attempt was to include a static dictionary in the class which the decorator adds to, but it will cause issues - as I expect the class to be inherited from multiple times. My second attempt was to add it to the PropertyDescriptor in the decorator method as:
export function clientAction(target: Game, propertyKey: string, descriptor: any) {
descriptor.custom = "Test"
}
but when I attempted to access it with Object.getOwnPropertyDescriptor(testGame.act, 'custom')
(where testGame
inherited from game
and act
was a method decorated with clientAction
), it returned undefined.
The issue is that the property descriptor cannot be used to keep extra data other than what it is providing, making it immutable. I would suggest using typescripts' reflect-meta
which is for this specific case, adding meta data to decorated properties. You can read more on it here and its npm link.
Here is a case example for its use:
Say you want to mark your decorators as you say so that you can reference the data in another place at say a later stage for instance, first you would need to write the function that will add the data.
// you add the args that will be needed in the function
export function name(data: any) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
Reflect.defineMetadata(
// this here is to reference the data later when we retrieve it.
propertyKey,
{
// we put this spread operator in case you have decorated already, so
// we dont want to lose the old data
...Reflect.getMetadata(propertyKey, target),
// then we append whatever else we need
name: data,
},
target,
);
return descriptor;
};
}
That said we can then define a class with some method and decorate it
class Name {
start = 'Hello';
@name('John Doe')
display() {
console.log(this.start);
}
}
So we have done the above, say now we would like to append the decorated string
to the start
property so that when it prints, it adds the string
in the decorator aswell.
// so first we get an instance of the class
const a = new Name();
// then we have the get the data
// this will get all the keys that have meta data, which will be 'display'
const keys = Reflect.getMetadataKeys(a);
// then we loop through to get that keys data,
keys.forEach((k) => {
const data = Reflect.getMetadata(k, a);
// now we have the meta data so we can append it.
a.start = `${a.start} ${data.name}`;
});
// and finally, we can call display
a.display(); // 'Hello John Doe'
This is a very useful construct provided by typescript, although experimental.