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?

3
  • Isn't this - no pun intended - a duplicate? In this particular case I'd use a temporary variable to store the result of $.proxy call. Commented Sep 12, 2016 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. Commented Sep 12, 2016 at 12:37
  • @raina77ow sorry, you were right, I was just doing something wrong in my code Commented Sep 12, 2016 at 12:44

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

Comments

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);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.