Could something like the following work?
class A {
private mySecretNumber = 2;
decorate (f: (x :number) => number) {
return (x: number) => f(this.mySecretNumber * x);
}
@(this.decorate)
method (x: number) {
return x + 1;
}
}
I tried with @this['decorate'], @A['decorate'], @A.decorate, can't find anything.
Here's an example of my use case: https://kutt.it/uOxVgM. Ideally I would just decorate getAbc() and get123().
There are many subtleties to your question.
The first thing is, yes, you can indeed use a method as a decorator, but not by writing @this.decorate (during transpiling, this would be the globalThis instead of A) or @A.decorate (decorate is not a static method, so A.decorate does not exist). The correct answer to this part of the question is @A.prototype.decorate; this will locate exactly what you have in mind.
The second thing is, when applying a decorator to a method of a class, the argument of the decorator is not the method function itself, but it actually have 3 arguments: a target object (which would be A.prototype in our case), a string (the name of the method, which is "method" in our case), and a property descriptor object. It also does not return a new function, but it should return a new property descriptor if not void. So inside the decorator function, you should try to modify the target object instead of trying to return a new function.
Putting things together, a working example will be:
class A {
private mySecretNumber = 2;
decorate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
var f = descriptor.value;
// Don't use arrow function here, otherwise "this"
// will not be the current A instance.
descriptor.value = function(x: number) {
return f(this.mySecretNumber * x);
};
Object.defineProperty(target, propertyKey, descriptor);
}
@A.prototype.decorate
method(x: number) {
return x + 1;
}
}
var a = new A();
console.log(a.method(3)); // 7
Based on your use case, I will use the following approach. Basically you use a static decorator to load the abstract decorate method, which can be implemented in subclasses. I modify the example above to give you an idea how it can be done.
abstract class A {
protected abstract decorate(x: number): number;
static decorate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
var f = descriptor.value;
descriptor.value = function(x: number) {
return f(this.decorate(x));
};
Object.defineProperty(target, propertyKey, descriptor);
}
@A.decorate
method(x: number) {
return x + 1;
}
}
class B extends A {
private mySecretNumber = 2;
protected decorate(x: number) { return this.mySecretNumber * x; }
}
var b = new B();
console.log(b.method(3)); // 7
Please note that this refers to the Legacy decorator implementation provided by TypeScript and older versions of Babel.
You could do this by implementing a decorator with a type constraint that limits its applicability to classes that implement the mySecretNumber property. This would fix both the syntax and the semantics of the scoping problems that Mu-Tsun Tsai explained so well.
Here is what I would prefer:
class A {
mySecretNumber = 2;
@decorate
method(x: number) {
return x + 1;
}
}
function decorate<T extends { mySecretNumber: number }, K extends keyof T>(
target: T, key: K,
descriptor: T[K] extends (n: number) => number ? TypedPropertyDescriptor<(n: number) => number> : never
) {
const f = descriptor.value;
if (f) {
descriptor.value = function (this: T, x: number) {
return f(this.mySecretNumber * x);
};
}
return descriptor;
}
This ensures that the decorated class has a mySecretNumber property of type number, binds that member to the desired this, and as a bonus ensures that the decorated method has the correct signature.
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