Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove event listener that has been added using bind(this)

How do I remove the click listener I bound to window in the constructor below? I need it to listen on window, and I need access to the button instance inside it.

class MyEl extends HTMLButtonElement {
  constructor() {
    super();
    this.clickCount = 0;
    window.addEventListener('click', this.clickHandler.bind(this));
  }
  
  clickHandler(e) {
    if (e.target === this) {
      this.textContent = `clicked ${++this.clickCount} times`;
      window.removeEventListener('click', this.clickHandler);
    }
  }
  
  disconnectedCallback() {
      window.removeEventListener('click', this.clickHandler);
  }
}

customElements.define('my-el', MyEl, { extends: 'button' });
<button is="my-el" type="button">Click me</button>
like image 679
connexo Avatar asked Feb 28 '19 08:02

connexo


People also ask

How do I get rid of add event listener?

Event listeners can also be removed by passing an AbortSignal to an addEventListener() and then later calling abort() on the controller owning the signal.

Should I remove event listeners before removing elements?

Removing the event listener first always results in lower memory usage (no leaks). Results: IE6 - memory leak.

What is Eventlistener in JavaScript?

The addEventListener() method allows you to add event listeners on any HTML DOM object such as HTML elements, the HTML document, the window object, or other objects that support events, like the xmlHttpRequest object.


3 Answers

It's not possible with your current implementation - every call of .bind creates a new separate function, and you can only call removeEventListener to remove a listener if the passed function is the same (===) as the one passed to addEventListener (just like .includes for arrays, or .has for Sets):

const fn = () => 'foo';
console.log(fn.bind(window) === fn.bind(window));

As a workaround, you could assign the bound function to a property of the instance:

class MyEl extends HTMLButtonElement {
  constructor() {
    super();
    this.clickCount = 0;
    this.boundListener = this.clickHandler.bind(this);
    window.addEventListener('click', this.boundListener);
  }
  
  clickHandler(e) {
    this.textContent = `clicked ${++this.clickCount} times`;
    window.removeEventListener('click', this.boundListener);
  }
}

customElements.define('my-el', MyEl, { extends: 'button' });
<button is="my-el" type="button">Click me</button>
like image 66
CertainPerformance Avatar answered Oct 28 '22 07:10

CertainPerformance


Create a wrapper func for your clickHandler like so.

class MyEl extends HTMLButtonElement {
  constructor() {
    super();
    this.clickCount = 0;
    this.wrapper = e => this.clickHandler.apply(this, e);
    window.addEventListener('click', this.wrapper);
  }
  
  clickHandler(e) {
    this.textContent = `clicked ${++this.clickCount} times`;
    
    window.removeEventListener('click', this.wrapper);
  }
}

customElements.define('my-el', MyEl, { extends: 'button' });
<button is="my-el" type="button">Click me</button>
like image 40
kemicofa ghost Avatar answered Oct 28 '22 07:10

kemicofa ghost


Another pattern is to keep your Listener inside the constructor.

To remove an Event Listener (no matter what pattern) you can add a 'remove' function the moment you create an Event Listener.

Since the remove function is called within the listen scope, it uses the same name and function

pseudo code:

  listen(name , func){
    window.addEventListener(name, func);
    return () => window.removeEventListener( name , func );
  }

  let remove = listen( 'click' , () => alert('BOO!') );

  //cleanup:
  remove();

Run Code Snippet below to see it being used with multiple buttons

Events bubbling UP & shadowDOM

to save you an hour once you do more with events...

Note that WebComponents (ie CustomElements with shadowDOM) need CustomEvents with the composed:true property if you want them to bubble up past its shadowDOM boundary

    new CustomEvent("check", {
      bubbles: true,
      //cancelable: false,
      composed: true       // required to break out of shadowDOM
    });

Removing added Event Listeners

Note: this example does not run on Safari, as Apple refuses to implement extending elements : extends HTMLButtonElement

class MyEl extends HTMLButtonElement {
  constructor() {
    let ME = super();// super() retuns this scope; ME makes code easier to read
    let count = 0;// you do not have to stick everything on the Element
    ME.mute = ME.listen('click' , event => {
      //this function is in constructor scope, so has access to ALL its contents
      if(event.target === ME) //because ALL click events will fire!
        ME.textContent = `clicked ${ME.id} ${++count} times`;
      //if you only want to allow N clicks per button you call ME.mute() here
    });
  }

  listen(name , func){
    window.addEventListener( name , func );
    console.log('added' , name , this.id );
    return () => { // return a Function!
      console.log( 'removeEventListener' , name , 'from' , this.id);
      this.style.opacity=.5;
      window.removeEventListener( name , func );
    }
  }
  eol(){ // End of Life
    this.parentNode.removeChild(this);
  }
  disconnectedCallback() {
      console.log('disconnectedCallback');
      this.mute();
  }
}

customElements.define('my-el', MyEl, { extends: 'button' });
button{
  width:12em;
}
<button id="One" is="my-el" type="button">Click me</button>
<button onclick="One.mute()">Mute</button> 
<button onclick="One.eol()">Delete</button> 
<br>
<button id="Two" is="my-el" type="button">Click me too</button>
<button onclick="Two.disconnectedCallback()">Mute</button> 
<button onclick="Two.eol()">Delete</button> 

Notes:

  • count is not available as this.count but is available to all functions defined IN constructor scope. So it is (kinda) private, only the click function can update it.

  • onclick=Two.disconnectedCallback() just as example that function does NOT remove the element.


Also see: https://pm.dartus.fr/blog/a-complete-guide-on-shadow-dom-and-event-propagation/

like image 43
Danny '365CSI' Engelman Avatar answered Oct 28 '22 06:10

Danny '365CSI' Engelman