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