Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a TypeScript method decorator and retain normal `this` scope

PLEASE NOTE: This issue was because of the use of GraphQL resolvers in running my decorated method. It meant that the scope of this was undefined. However, the basics of the question are useful for anyone hitting a problem with decorators.


This is a basic decorator I'm wanting to use (mine has more code in it):

const someDecorator = (argPassed: any) => {

  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {

    const originalMethod = descriptor.value;

    // DO stuff here...
    console.log(argPassed);

    // Wrapping the original method
    descriptor.value = (...args: any[]) => {

      const result = originalMethod.apply(this, args);

      return result;
    };
  };
};

I am using arrow functions within the decorator which is the only way i could get it to return some kind of scope, albeit different to a normal this scope.

This is the class I'm using and the method I'm decorating:

class SomeClass {

  constructor() {
  }

  @someDecorator('Passing this in...')
  public doingSomething(argPassed: string) {

    console.log(this); // Returns: { default: SomeClass { otherMethodInMyClass: [Function] } }

    //  Meaning i can't do this
    //  this.otherMethodInMyClass is not a function
    this.otherMethodInMyClass(argPassed);

  }

  private otherMethodInMyClass = (argPassed: any) => {
    // Let's go for it...
  }

}

Currently the decorator passes back the scope of doingSomething as:

{ default: SomeClass { otherMethodInMyClass: [Function] } }

When not using the decorator I get:

SomeClass { doingSomething: [Function], otherMethodInMyClass: [Function] }

Is this normal behaviour? If not, what am I doing wrong? If so, how do I allow my method to use its own scope as I am calling other methods after.

Update: As @jcalz rightly mentioned, an arrow function doesn't get its own this context. However when I used a non-arrow function across the decorator this returned as undefined.

Thanks in advance

like image 419
jamesmhaley Avatar asked May 17 '19 15:05

jamesmhaley


People also ask

How do you use a TypeScript decorator?

In TypeScript, you can create decorators using the special syntax @expression , where expression is a function that will be called automatically during runtime with details about the target of the decorator. The target of a decorator depends on where you add them.

Are decorators still experimental in TypeScript?

As of writing this guide, decorators are still an experimental feature in TypeScript. To enable this feature, set the experimentalDecorators compiler flag either on the command line or in your tsconfig. json file.

Where can TypeScript decorators be applied to?

A Decorator is a special kind of declaration that can be applied to classes, methods, accessor, property, or parameter.

What is target in decorator TypeScript?

target: Constructor function of the class if we used decorator on the static member, or prototype of the class if we used decorator on instance member. In our case it is firstMessage which is an instance member, so the target will refer to the prototype of the Greeter class. propertyKey: It is the name of the property.


1 Answers

The issue in your question seems to be that you're using an arrow function as a method, which is not advisable since arrow functions don't get their own this context.

You went on to say that changing this doesn't fix your issue, but I can't reproduce that:

const someDecorator = (argPassed: any) => {
    return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
        const originalMethod = descriptor.value;
        console.log("ARGPASSED: ");
        console.log(argPassed);
        // anonymous function, not arrow
        descriptor.value = function (...args: any[]) {
            const result = originalMethod.apply(this, args);
            return result;
        };
    };
};

class SomeClass {    
    constructor() { }

    @someDecorator('Passing this in...')
    public doingSomething(argPassed: string) {   
        console.log("THIS: ")
        console.log(this); 
        this.otherMethodInMyClass(argPassed);
    }

    private otherMethodInMyClass = (argPassed: any) => { }
}

new SomeClass().doingSomething("abc");
// ARGPASSED: 
// Passing this in...
// THIS: 
// Object { otherMethodInMyClass: otherMethodInMyClass() }

Link to code in Playground

Looks good to me. If your issue persists, you might want to include more details about your configuration in your question. It always helps to make sure that code in your questions constitutes a reproducible example. Good luck!

like image 177
jcalz Avatar answered Oct 05 '22 15:10

jcalz