Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: Can a class method be a decorator of another method?

Tags:

typescript

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().

like image 558
jeanpaul62 Avatar asked Sep 02 '25 01:09

jeanpaul62


2 Answers

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

Update:

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
like image 154
Mu-Tsun Tsai Avatar answered Sep 14 '25 08:09

Mu-Tsun Tsai


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.

like image 37
Aluan Haddad Avatar answered Sep 14 '25 09:09

Aluan Haddad



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!