For years I ran into problems trying to remove an event listener in JavaScript. Often I would have to create an independent function as the handler. But that is just sloppy and, especially with the addition of arrow functions, just a pain.
I am not after a ONCE solution. This needs to work in all situations no matter HOW the callback is defined. And this needs to be raw JS so anyone can use it.
The following code works fine since the function clickHandler
is a unique function and can be used by both addEventListener
and removeEventListener
:
This example has been updated to show what I have run into in the past
const btnTest = document.getElementById('test');
let rel = null;
function clickHandler() {
console.info('Clicked on test');
}
function add() {
if (rel === null) {
rel = btnTest.addEventListener('click', clickHandler);
}
}
function remove() {
btnTest.removeEventListener('click', clickHandler);
}
[...document.querySelectorAll('[cmd]')].forEach(
el => {
const cmd = el.getAttribute('cmd');
if (typeof window[cmd] === 'function') {
el.addEventListener('click', window[cmd]);
}
}
);
<button cmd="add">Add</button>
<button cmd="remove">Remove</button>
<button id="test">Test</button>
You used to be able to do it with arguments.callee
:
var el = document.querySelector('#myButton');
el.addEventListener('click', function () {
console.log('clicked');
el.removeEventListener('click', arguments.callee); //<-- will not work
});
<button id="myButton">Click</button>
But using an arrow function does not work:
var el = document.querySelector('#myButton');
el.addEventListener('click', () => {
console.log('clicked');
el.removeEventListener('click', arguments.callee); //<-- will not work
});
<button id="myButton">Click</button>
Is there a better way??
UPDATE
As stated by @Jonas Wilms this way will work:
var el = document.querySelector('#myButton');
el.addEventListener('click', function handler() {
console.log('clicked');
el.removeEventListener('click', handler); //<-- will work
});
<button id="myButton">Click</button>
Unless you need to using binding:
var obj = {
setup() {
var el = document.querySelector('#myButton');
el.addEventListener('click', (function handler() {
console.log('clicked', Object.keys(this));
el.removeEventListener('click', handler); //<-- will work
}).bind(this));
}
}
obj.setup();
<button id="myButton">Click</button>
The problem is that there are too many ways to provide an event handler to the addEventListener
function and your code might break if the way you pass in the function changes in a refactor.
You can NOT use an arrow function or any anonymous function directly and expect to be able to remove the listener.
To remove a listener requires you pass the EXACT SAME ARGUMENTS to
removeEventListener
as you passed toaddEventListener
but when you use an anonymous function or an arrow function you do not have access to that function so it's impossible for you to pass it intoremoveEventListener
works
does not work
your choices are
One example is @Intervalia closure. He tracks the function and other arguments you passed in and returns a function you can use the remove the listener.
One solution I often use which often fits my needs is a class that tracks all the listeners and remove them all. Instead of a closure it returns an id but it also allows just removing all listeners which I find useful when I build up something now and want to tear it down something later
Usage would be something like
note the code above is ES6 and would have to be changed to support really old browsers but the ideas are the same.
There is an easy solution using closures.
By moving the code to both
addEventListener
andremoveEventListener
into a single function you can accomplish the task easily:The function
ael
above allows the element, the event type and the callback to all be saved in the closure scope of the function. When you callael
it callsaddEventListener
and then returns a function that will callremoveEventListener
. Later in your code you call that returned function and it will successfully remove the event listener without worrying about how the callback function was created.Here is an es6 version:
Just use a named function expression:
For sure that can be wrapped in a function:
You can use the
once
option ofEventTarget.addEventListener()
:Note: supported by all browsers but IE.