1

I am using jQuery inside my TypeScript class.

export class RedTextBox {
 private element : HTMLInputElement;
 private changed : Boolean;

 Attach(textbox : HTMLInputElement) {
   this.element = textbox;
   ChangeColor();
 }

 ChangeColor() {
  $(this.element).css("background", "red");

  $(this.element).on("change", function() { 
       this.Changed = true;  // 'this' here is not the object of Typescript      
   });
 }
}

In the above code I am getting an error saying this.Changed is not defined. I checked in debugger to find out that inside the jQuery event, 'this' is actually the HTML Element instead of TypeScript object. Is there an alternate 'this' or can we force jQuery to leave 'this' alone (no pun intended)?

fahadash
  • 3,133
  • 1
  • 30
  • 59
  • Possible duplicate of [How to access the correct \`this\` inside a callback?](https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback) – Heretic Monkey Aug 17 '18 at 18:22

3 Answers3

3

When you use function to define an anonymous function it's this is not bound to the instance you defined it in, and jQuery leverages call/apply to give a value to this inside of those callbacks.

The easiest solution is to swap to arrow functions:

$(this.element).on("change", () => {
  this.Changed = true;      
});

When you define an arrow function, the value of this inside of the arrow function is tied to the value of this in the scope the function is defined.

Another, less desirable alternative is the classic usage of that or self:

const self = this;
$(this.element).on("change", function() {
  self.Changed = true;      
});

EDIT

One final note, if you already have a function and can't use one of the above options for accessing the context of the outer scope, you can force this to be what you want inside of a function using Function.prototype.bind. This example is mostly relevant if your event handlers/callbacks are members of a class.

ChangeColor() {
  $(this.element).on("change", this.HandleChange.bind(this))
}

HandleChange() {
  this.Changed = true;
}

This only works with the call to bind, if you forgot to bind it then you would end up with the same issue you started with. Also, binding on the fly like this can get costly as a new function has to be created every time bind is called. So generally you pre-bind your functions you hand out in the constructor.

class RedTextBox {
  constructor() {
    this.HandleChange = this.HandleChange.bind(this);
  }

  ChangeColor() {
    $(this.element).on("change", this.HandleChange);
  }

  HandleChange() {
    this.Changed = true;
  }
}

Notice how I only bind once in the constructor and then I freely pass the reference to this.HandleChange as the callback.

Brandon Buck
  • 7,177
  • 2
  • 30
  • 51
  • Can we use the `arrow functions` while still transpiling to ES5? I have to support IE9+ – fahadash Aug 17 '18 at 15:38
  • Generally speaking, yes. The transpiler should have have target rules for translating arrow functions to ES5. The second option is ES5 compatible and probably close to what you might end up with. – Brandon Buck Aug 17 '18 at 15:39
2

Well, It's not jquery that's hijacking. It's how javascript works. When you pass along a callback, the callback 'caller' "takes" the scope "this" to itself. When using ES6 or later (if I'm not wrong) you can use => functions and, by doing this, you KEEP the contex. Like this:

export class RedTextBox {
private element : HTMLInputElement;
 private changed : Boolean;

 Attach(textbox : HTMLInputElement) {
   this.element = textbox;
   ChangeColor();
 }

 ChangeColor() {
  $(this.element).css("background", "red");

  $(this.element).on("change", /* THIS PART */() => { 
       this.Changed = true;  // 'this' here is not the object of Typescript      
   });
 }
}
Eduardo Elias Saléh
  • 806
  • 1
  • 11
  • 23
2

This behavior is the standard way Javascript functions work. If you pass a function to jQuery, jQuery will be the one deciding what this to pass to the function, even if the function was originally a method of a class.

ES2015 add arrow functions (=>) which capture this from the declaration context and this is probably what you should use:

$(this.element).on("change", () => { 
   this.Changed = true;  // this is capture from the class 
});
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357