Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript, how to keep class methods event handlers context to "this" instance

I have an issue with Classes in TypeScript. each time I have to listen to an HTML Element events I need to use Function.bind() to connect it to the current instance.

class VideoAdProgressTracker extends EventDispatcher
{
    private _video:HTMLVideoElement;

    constructor(video:HTMLVideoElement)
    {
        super();
        this._video = video;
        this._video.addEventListener("timeupdate", this.handleTimeUpdateEvent);
    }

    private handleTimeUpdateEvent(event)
    {
        // Something
    }
}

I don't have to save the bound anonymous function each time when you have 5-10 events it will become a mess. I want to just have it bound.

any suggestions?

like image 645
Nisim Joseph Avatar asked Nov 26 '16 19:11

Nisim Joseph


People also ask

What's the difference between an event handler and an event listener?

Note: Event handlers are sometimes called event listeners — they are pretty much interchangeable for our purposes, although strictly speaking, they work together. The listener listens out for the event happening, and the handler is the code that is run in response to it happening.

Why this is undefined in event handler?

Why is this undefined In React event handler? This is because when you use an arrow function, the event handler is automatically bound to the component instance so you don't need to bind it in the constructor. When you use an arrow function you are binding this lexically.

What is addEventListener in TypeScript?

The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target. Common targets are Element , or its children, Document , and Window , but the target may be any object that supports events (such as XMLHttpRequest ).


2 Answers

You can use arrow functions for the listener methods:

class VideoAdProgressTracker extends EventDispatcher {
    private _video:HTMLVideoElement;

    constructor(video:HTMLVideoElement) {
        super();
        this._video = video;
        this._video.addEventListener("timeupdate", this.handleTimeUpdateEvent);
    }

    private handleTimeUpdateEvent = (event) => {
        // Something
    }
}

This will work fine, unless you want to extend this class and override one of these methods.
The reason for this is that using arrow functions you don't really have methods, just properties that are assigned with arrow functions, they are not part of the prototype.

For example:

class A {
    fn1 = () => { }
    fn2() { }
}

Compiles to:

var A = (function () {
    function A() {
        this.fn1 = function () { };
    }
    A.prototype.fn2 = function () { };
    return A;
}());

So if you don't care about being able to easily override one of these methods, then use this method.

If you want to stay with methods but don't want to manually bind all methods, then you can:

constructor(video:HTMLVideoElement) {
    super();
    this._video = video;

    for (let key in this) {
        if (typeof this[key] === "function") {
            this[key] = this[key].bind(this);
        }
    }

    this._video.addEventListener("timeupdate", this.handleTimeUpdateEvent);
}

You can also check the function name and somehow prefix methods you'd like to bind.

like image 154
Nitzan Tomer Avatar answered Oct 11 '22 10:10

Nitzan Tomer


You can also cache the controller context. I use this style a lot, when working with d3.js. This way I still have access to the context of the callback, which in d3 refers usually to a DOM element.

private onClick(): Function {
  controller: this = this;
  return function(event) {
    controller.anotherClassFunction();
  };
}

private secondFunction(): void {
  this.addEventlistener(this.onClick());
}
like image 27
wasserholz Avatar answered Oct 11 '22 11:10

wasserholz