daniel.sedlacek daniel.sedlacek - 11 months ago 47
TypeScript Question

TypeScript class and JS scope confusion (again)

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 (
) 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?

Answer Source

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.