Andrej Kaurin
Andrej Kaurin

Reputation: 11642

Typescript decorator and this context

I am using decorators in typescript/angular in the following way

export function Field({source}){
  return (target, property) => {
    // Some code here
  }
}

Then I want to use it this way

 export class MyClass {

  constructor(private myService: MyService) {}

  @Field({source: () => this.myFn()})
  myProp: string;

  private myFn() { 
   // another code 
   return this.myService.get()
  }
}

Obviously the context is wrong and "this" does not refer to the instance of MyClass. What is the best way to link this context with the instance of MyClass?

Upvotes: 1

Views: 1393

Answers (1)

Daniel Gimenez
Daniel Gimenez

Reputation: 20494

You can get access to instances in decorators with roundabout methods depending on what you're trying to do. In the example below the function passed to the decorator is called every time the property is set.

The decorator will work for both properties and fields. If a field is being decorated then the prototype of the target is modified and the field is converted into a property with a hidden backing variable to store the property value.

Note how this does not use arrow functions to define the getter and setter. This is how it the instance can be retrieve at the time the property/field is getting set. Personally, I use arrow functions so much I forget something like this was even possible until I tried it out.

function Field(srcFunc) {
  return function (target: any, propertyKey: string, descriptor?: PropertyDescriptor) {
    if (descriptor == null) {
      const backingKey = `__${propertyKey}`;
      Object.defineProperty(target, backingKey, { enumerable: false, writable: true });
      Object.defineProperty(target, propertyKey, {
        configurable: true,
        enumerable: true,
        get: function() {
          return this[backingKey];
        },
        set: function(value) {
          this[backingKey] = value;
          srcFunc.call(this);
        }
      });
    }
    else {
      const setOriginal = descriptor.set;
      descriptor.set = function(value) {
        setOriginal.call(this, value);
        srcFunc.call(this);
      }
    }
  }
}

export class MyClass {

  @Field(MyClass.prototype.myFn)
  myProp: string;

  private myFn() { 
   // another code 
  }
}

Upvotes: 1

Related Questions