select your favorite theme:

Jonas Fährmann

Custom Elements, event listener and this

01 Feb 2021

I use Custom Elements a lot. I also like to use .bind(this) in Custom Element event listeners for several reasons. Why this can be a problem if the event listener needs to be removed and how a workaround could look like, i will explain in this post.

Custom Elements

Custom Elements are great. You can extend the generic HTMLElement or more specific HTMLParagraphElement, HTMLButtonElement and so on. To register a custom element you use the define() method with a string for the name and a class object. The class object is written using standard ES 2015 class syntax:

class MyCustomElement extends HTMLElement {
    constructor() {
        // Always call super first in constructor
        super();
    }
    // Element functionality down here
}
customElements.define("my-custom-element", MyCustomElement);

This is a very basic example. You can read more about using Custom Elements on MDN.

Use Case: Event Listener

Imagine you will register a click listener for a button, like this:

class MyCustomElement extends HTMLElement {
    constructor() {
        super();
    }

    connectedCallback() {
        this.querySelector(".my-button").addEventListener("click", this.handleButtonClick.bind(this));
    }

    handleButtonClick(event) {
        // some action on button click
        this.anotherMethod();
    }

    anotherMethod() {
        // some more action
    }
}
customElements.define("my-custom-element", MyCustomElement);

Maybe you have already recognized .bind(this) at the end of the passed method handleButtonClick(). In an event listener this normally corresponds to the EvenTarget (event.target). The bind method is used to ensure that this corresponds to the instance of the custom element instead. If you don't do this, you can not call any other methods of the Custom Element instance, e.g. this.anotherMethod() won't be executed. If you perform console.log(this), the code from above should output the Custom Element and not the button element.

The Problem: removeEventListener

The example above works fine until you want to remove the eventListener. The following code won't work:

class MyCustomElement extends HTMLElement {
    constructor() {
        super();
    }

    connectedCallback() {
        this.querySelector(".my-button").addEventListener("click", this.handleButtonClick.bind(this));

        this.querySelector(".my-button").removeEventListener("click", this.handleButtonClick.bind(this));
    }

    handleButtonClick(event) {
        // some action on button click
        this.anotherMethod();
    }

    anotherMethod() {
        // some more action
    }
}
customElements.define("my-custom-element", MyCustomElement);

The removeEventListener() has no effect, the event for the button remains. The reason for this is that removeEventListener() expects the exact same method (===) as passed in addEventListener(). With bind this is simply not possible, because it creates a new function when called:

The bind() function creates a new bound function, which is an exotic function object (a term from ECMAScript 2015) that wraps the original function object. Calling the bound function generally results in the execution of its wrapped function. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind/

The Solution: boundedListener

With a bounded listener property you can get around the problem. Instead of passing the method directly you can create a boundListener in the constructor(), like this:

class MyCustomElement extends HTMLElement {
    constructor() {
        super();
        this.buttonBoundListener = this.handleButtonClick.bind(this));
    }

    connectedCallback() {
        this.querySelector(".my-button").addEventListener("click", this.buttonBoundListener;
        this.querySelector(".my-button").removeEventListener("click", this.buttonBoundListener;
    }

    handleButtonClick(event) {
        // some action on button click
        this.anotherMethod();
    }

    anotherMethod() {
        // some more action
    }
}
customElements.define("my-custom-element", MyCustomElement);

Now this.buttonBoundListener is a property of the Custom Element instance. You can use it to add and remove EventListener to the button element and you are able to use this as the instance of the Custom Element.
And that's it. Thank you for reading.

More articles and notes