1

I thought the JS scope can not surprise me ever again but here we are.

I am using this little JQuery plugin for observer like pattern. This is how I subscribe and unsubscribe a handler (listener) from custom events

$.observer.subscribe('my-event', this.myHandler);
$.observer.unsubscribe('my-event', this.myHandler);

public myHandler() {
    console.log("hello World", this);
}

The problem is that if I use a class method as a callback (this.myHandler) that method will be invoked not within the class scope but within the observer object scope.

When I use JQuery's proxy method like this:

$.observer.subscribe('my-event', $.proxy(this.myHandler, this));
$.observer.unsubscribe('my-event', $.proxy(this.myHandler, this));

the scope in the handler is correct but the unsubscribe method stops working, apparently with the proxy I am unsubscribing from something else, not the handler method.

I can not use the old JS trick of defining a local variable and use it instead of this, like

var o = this;
public myHandler() {
    console.log("hello World", o);
}

because this is a typescript class and the var has different meaning.

What should I do?

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
daniel.sedlacek
  • 8,129
  • 9
  • 46
  • 77
  • Isn't [this](http://stackoverflow.com/questions/20627138/typescript-this-scoping-issue-when-called-in-jquery-callback) - no pun intended - a duplicate? In this particular case I'd use a temporary variable to store the result of `$.proxy` call. – raina77ow Sep 12 '16 at 12:30
  • @raina77ow the arrow function has the same problem as the &.proxy method, the unsubscribe will not work - and I don't understand why. – daniel.sedlacek Sep 12 '16 at 12:37
  • @raina77ow sorry, you were right, I was just doing something wrong in my code – daniel.sedlacek Sep 12 '16 at 12:44

2 Answers2

3

What should work is to declare the handler as a property:

//public myHandler() {
public myHandler = () => {
    console.log("hello World", this);
}

this has a side-effect... myHandler is property and cannot be overridden. But we can call another method (toBeOverriddenHandler) in our handler, which will have proper scope (this) and could be overridden

public myHandler = () => {
    this.toBeOverriddenHandler()
}

// could be changed in subtypes
protected toBeOverriddenHandler () {
    console.log("hello World", this);
}
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
3

The reason that this happens is because $.proxy(this.myHandler, this) returns a new function each time you invoke it. In order for unsubscribe to work you need to pass it the same function that you passed to subscribe (this is the case with almost any listener interface, including the DOM ones).

const handler = $.proxy(this.myHandler, this);
$.observer.subscribe('my-event', handler);
$.observer.unsubscribe('my-event', handler);

Alternatively, you can create your method as an arrow function property in your constructor, or automatically bind it:

class YourClass {
  constructor() {
    this.myHandler = () => {};
    // or
    this.myHandler = this.myHandler.bind(this);
  }
}

Then you can just pass this.myHandler into observer.subscribe and it will do the right thing.

Sean Vieira
  • 155,703
  • 32
  • 311
  • 293