Remove a specific EventListener that has been defined within a class

I am struggling to successfully remove an eventListener, but it seems like I'm missing a crucial step.

Could somebody please point out why the code below is not effective in removing the event listener from the button?

Even after attempting to bind 'this' for scope purposes, I still can't get it to work as intended.

class Test {

  handleEvent(e) {
    console.log(e.target.id)
    alert()

    // This doesn't work
    e.target.removeEventListener("click", this.handleEvent)

    // Nor does this
    document.getElementById(e.target.id).removeEventListener("click", this.handleEvent)
  }
  constructor() {
    let b = document.getElementById("b")
    b.addEventListener("click", this.handleEvent)

    //b.addEventListener("click", this.eventHandler.bind(this) )
  }
}

new Test()
<button id="b">
Click Me
</button>

Answer №1

Utilizing prototype methods as event handlers can pose challenges, especially when you require both the bound this value associated with the instance and a reference to the actual event handler function.

Typically, the event queue executes the handler within the context of the element to which the event was initially attached. While it's possible to alter this context, doing so necessitates creating a new function, thereby disconnecting it from the method in the prototype.

To maintain a concise class structure, one approach is to define the event handler methods as unique properties of the instance that cannot be inherited. An easy solution involves defining these methods as arrow functions directly within the constructor.

class Test {
  constructor() {
    this.eventHandler = e => {
      console.log(e.target.id);
      e.target.removeEventListener("click", this.eventHandler);
    };
    let b = document.getElementById("b");
    b.addEventListener("click", this.eventHandler);
  }
}

new Test();
<button id="b">Click me!</button>

The arrow function retains the lexical environment in which it was defined, preventing the event queue from overriding the context. This ensures that the this keyword in the handler function is correctly linked to the instance, while maintaining a reference to the attached event handler function.

An alternative approach, slightly less memory-intensive, involves using bind when defining the custom property:

class Test {
  constructor() {
    this.eventHandler = this.eventHandler.bind(this);
    let b = document.getElementById("b");
    b.addEventListener("click", this.eventHandler);
  }
  eventHandler (e) {
    console.log(e.target.id);
    e.target.removeEventListener("click", this.eventHandler);
  }
}

In this scenario, bind generates a new function object that points to the method in the prototype without duplicating its code. This technique is similar to explicitly calling the prototype method:

this.eventHandler = e => Test.prototype.eventHandler.call(this, e);

It's important to note that shadowing an underlying prototype property with a similarly named own property within the instance will not override the prototype property; instead, it will simply coexist alongside it, ensuring multiple instances of the class continue to function correctly.

Another option involves developing your own "event model" that encapsulates wrapper functions for all events, mimicking the behavior seen in the previous code example. These wrappers utilize call to bind the desired this value to the event handler, and stored function references are employed for removing events. Constructing such a model offers insight into how this binding operates and enhances understanding of the native event model.

Answer №2

The original poster's code is experiencing issues for two main reasons.

  • In one case, the prototypal eventHandler does not have the correct this context.
  • In another scenario where this.eventHandler.bind(this) is used, a new handler function is created without a saved reference to it. This means that with removeEventHandler, the correct event handler is never referenced.

A possible solution could be...

function handleTestClickEvent(evt) {

  console.log(evt.currentTarget);
  console.log(this);
  console.log(this.eventHandler);

  // remove the instance-specific (`this` context) `eventHandler`.
  evt.currentTarget.removeEventListener('click', this.eventHandler);
}

class Test {
  constructor() {
    // create own eventHandler with bound `this` context.
    this.eventHandler = handleTestClickEvent.bind(this);

    document
      .querySelector('#b')
      .addEventListener('click', this.eventHandler);
  }
}
new Test();
<button id="b">click me</button>

Another potential approach would be to use an arrow-function-based, instance-specific event handler. Arrow functions do not support explicit this binding and always refer to the context in which they are implemented.

class Test {
  constructor() {
    // arrow-function based, thus instance-specific event handler.
    this.eventHandler = evt => {

      console.log(evt.currentTarget);
      console.log(this);

      evt.currentTarget.removeEventListener('click', this.eventHandler);
    }
    document
      .querySelector('#b')
      .addEventListener('click', this.eventHandler);
  }
}
new Test();
<button id="b">click me</button>

However, both approaches demonstrate that implementing a reference-specific event handler using the prototype is not the recommended approach.

Given the scenario presented by the original poster, I would lean towards the first solution as it allows for code reuse through the locally implemented handleTestClickEvent. It also has a smaller footprint in terms of the instance-specific this.eventHandler, which is created from handleTestClickEvent.bind(this) in the former approach, while the latter provides a complete handler implementation for each instance.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Retrieving Data from a JSON Object Using a Specific Key

Received a JSON response similar to the one below { "SNGS": { "$": { "xmlns": "csng", "xmlns:ns2": "http://www.w3.org/1999/xlink" }, "Defec ...

Is there a way to specify the login response details when making an Angular API request?

I am currently using Angular to connect to my backend login service. However, I am facing an issue with setting the popup message when the username or password is incorrect. I want to display the detailed message from the API on my login page when any erro ...

Instantly change the default value with Ajax selection

My current setup involves using Ajax to populate a select HTML element with values from an array returned by PHP. Here is the Ajax code snippet: <script type = "text/javascript"> $(document).ready(function(){ $('#fds_categories&ap ...

Exploring through a table using JavaScript

I am struggling to search a table within my HTML for a specific term. The code I have works to an extent, but it does not account for alternatives such as searching for "what is that" when I type in "What is that". Additionally, I need the script to ignor ...

The gear icon in the video player is not showing up when I try to change the

I am currently trying to implement a feature that allows users to select the quality of the video. I am using videojs along with the videojs-quality-selector plugin, but even though the video runs successfully, the option to choose the quality is not appea ...

Breaking apart a plane geometry using Three.js shatter/explode effects

Struggling to achieve a glass shattering effect in Three.js using Tween and plane geometry. The issue arises when the mesh/geometry fails to update with the tween after the initial rendering. It seems that calling the function "shatter()" before the first ...

What is the reason behind the component property being undefined when accessed in a different function?

I'm currently developing an Angular project that involves user authentication. I encountered an issue where the property that I set for the authenticated user in the NgOnInit() function becomes undefined when trying to access it in another function ev ...

What could be the reason why Three.js animation doesn't seem to function properly within a <div> element but works perfectly fine within the <body> of an HTML document

Having trouble getting a Three.js animation to work inside a div? It works fine when placed directly on the body, right? The issue seems to be related to how it is appended. The JavaScript code has a notable difference where the container is appended: do ...

Customize jQuery Autocomplete choices depending on another jQuery Autocomplete input

I need to implement a feature where users can select a company and then an employee from that company. I came across a similar question on this link, but I specifically want both inputs to be autocomplete-enabled. This is the code snippet I currently have ...

Is it possible to close a MongoDB connection using a different method other than the one mentioned in this function?

I am facing a challenge with uploading a substantial data set from my node.js application to a mongodb. The process involves running a for loop to fetch each result. Initially, I established the connection to the MongoDB within every iteration of the loop, ...

How can I search across different fields within a single collection using meteor-autocomplete?

I have implemented mizzao/meteor-autcomplete to retrieve matching items from a MongoDB collection based on user input. While I can successfully search for items in one field, I am facing difficulty searching multiple fields within the same collection. My ...

What are some ways to streamline this D3 script?

My CSV data displays pass rates by organisation for different years: org,org_cat,2004_passed,2004_total,2005_passed,2005_total,2006_passed,2006_total GSK,industry,35,100,45,100,55,100 I am using D3 and aiming to create a dictionary of organisations struc ...

Looking to save a CSS element as a variable

I am working on improving the readability of my code in Protractor. My goal is to assign a CSS class to a variable and then use that variable within a click method. element.all(by.css("div[ng-click=\"setLocation('report_road')\"]")).cl ...

I am interested in utilizing Google Apps Script (GAS) to store images in GoogleDrive and automatically populate the corresponding URL in a

Currently, I am utilizing GoogleAppsScript to develop a form for submitting names and images. The idea is to store the submitted name and image in GoogleSpreadSheet while also storing the image in GoogleDrive along with its destination URL. The process inv ...

Creating unique div IDs dynamically in PHP, JavaScript, and MySQL

I'm currently working with an ajax code that fetches data from table columns when a specific data is selected from the dropdown menu. In my surveycontent.php file, I have the following script: <script type="text/javascript"> function show ...

JavaScript error: Trying to access property 'startsWith' of an undefined value - discord bot

I have limited knowledge of javascript, being more experienced in Java. I wanted to create a simple Discord bot that could send messages randomly at different times. After piecing together code from multiple tutorials, here is what I currently have: var D ...

Implementing Google Ads Code in NextJS for Automated Units

I'm currently working on a NextJS project and I need to integrate the Google AdSense code for automatic ads. The Google ad code I have is: <script async src={`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${process.env. ...

Troubleshooting a Laravel method invoked in JavaScript using PhpStorm

I'm seeking some advice on how to debug a Laravel function mapped in a JavaScript function that is being called in an HTML page. $('#upload-avatar').fileapi({ url: '{{ route("user.avatar") }}', accept: 'image/*&a ...

What is preventing me from accessing an object within an array in Redux?

After fetching an array of objects from an API and storing them in the state, I encountered an issue. While I was able to successfully load the entire array into my state and view it in a console log, I faced trouble accessing specific values from the keys ...

How can you utilize jQuery to iterate through nested JSON and retrieve a specific matching index?

In the scenario where I have a nested JSON object like this: var library = { "Gold Rush": { "slides": ["Slide 1 Text","Slide 2 Text","Slide 3 Text","Slide 4 Text"], "bgs":["<img src='1.jpg' />","","<img src='2.j ...