I am trying to call a function that that has a decorator attached to it. What it does is run every x
seconds which works just fine, however, when I try to log the properties in the function they output undefined
, but in the constructor it displays the proper class.
If I call another function that is in the class, the function gets called, the issue just seems to be when accessing properties.
Here are a few of the ways I have tried to call the method:
export function Repeat(interval: number, delay: number = 0) { return (target: any, prop: any, descriptor: PropertyDescriptor) => { timer(delay * 1000, interval * 1000) .pipe( tap(i => descriptor.value.call(target, i)), tap(i => target[prop].call(target, i)), tap(i => target[prop].bind(target)(i)) ) .subscribe(); }; }
Here is how I am implementing the decorator:
export class MyClass { constructor(private readonly v: Item) { console.log(this.v); // outputs: typeof Item } @Repeat(1) repeat(count: number) { console.log(this.v); // outputs: undefined this.testFunc(); // runs the function } testFunc(){ console.log('test function'); } }
Here is a stackblitz example
Edit
After playing for a bit, I can see that I can do this:
export function Repeat(interval: number, delay = 0) { return function (target: any, prop: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (this: GameObject, ...args: any[]) { const t = timer(delay * 1000, interval * 1000) .pipe(map<number, boolean>(i => originalMethod.apply(this, [i]))) .subscribe(); }; return descriptor; }; }
Then when I call it in the class the repeater works:
export class MyClass { constructor(private readonly v: Item) { this.repeat(0); } @Repeat(1) repeat(count: number) { console.log(this.v); // outputs: typeof Item } }
However, this is not what I want to resort to, I would like the function to be called automatically and not manually.
Advertisement
Answer
So, to solve my issue I had to use a decorator at the class level along with at the method level. Then, in the constructor I run the method on the parent class.
First import the reflect-metadata
module:
import 'reflect-metadata';
export function Component() { return <T extends { new (...args: any[]): any }>(target: T): any => { return class extends target { private methods: string[] = []; constructor(...args: any[]) { super(...args); this.methods = Reflect.ownKeys(target.prototype) as string[]; this.runRepeater(); } runRepeater() { this.methods.forEach((method) => { const { delay, interval } = Reflect.getMetadata('token', target.prototype, method) || {}; if (!delay || !interval) return; timer(delay * 1000, interval * 1000) .pipe(map<number, boolean>((i) => super[method]())) .subscribe(); }); } }; }; }
Here is the repeat function:
export function Repeat(interval: number, delay = 0) { return function (target: any, prop: string, descriptor: PropertyDescriptor) { Reflect.defineMetadata('token', { interval, delay }, target, prop); }; }
Here is how it is implemented in the class:
@Component() export class MyClass { constructor(private readonly v: Item) { console.log(this.v); // outputs: typeof Item } @Repeat(1) repeat(count: number) { console.log(this.v); // outputs: typeof Item } }
Here is the stackblitz example.