How can I remove an event listener no matter how the callback is defined
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.
javascript ecmascript-6 es2017
add a comment |
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.
javascript ecmascript-6 es2017
Or you just use jQuerys$(sth).once("click", ...)
...
– Jonas Wilms
Nov 12 '18 at 16:16
add a comment |
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.
javascript ecmascript-6 es2017
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.
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>
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>
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>
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>
var el = document.querySelector('#myButton');
el.addEventListener('click', () => {
console.log('clicked');
el.removeEventListener('click', arguments.callee); //<-- will not work
});
<button id="myButton">Click</button>
var el = document.querySelector('#myButton');
el.addEventListener('click', () => {
console.log('clicked');
el.removeEventListener('click', arguments.callee); //<-- will not work
});
<button id="myButton">Click</button>
var el = document.querySelector('#myButton');
el.addEventListener('click', function handler() {
console.log('clicked');
el.removeEventListener('click', handler); //<-- will work
});
<button id="myButton">Click</button>
var el = document.querySelector('#myButton');
el.addEventListener('click', function handler() {
console.log('clicked');
el.removeEventListener('click', handler); //<-- will work
});
<button id="myButton">Click</button>
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>
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>
javascript ecmascript-6 es2017
javascript ecmascript-6 es2017
edited Nov 16 '18 at 17:02
Intervalia
asked Nov 12 '18 at 16:10
IntervaliaIntervalia
4,15711032
4,15711032
Or you just use jQuerys$(sth).once("click", ...)
...
– Jonas Wilms
Nov 12 '18 at 16:16
add a comment |
Or you just use jQuerys$(sth).once("click", ...)
...
– Jonas Wilms
Nov 12 '18 at 16:16
Or you just use jQuerys
$(sth).once("click", ...)
...– Jonas Wilms
Nov 12 '18 at 16:16
Or you just use jQuerys
$(sth).once("click", ...)
...– Jonas Wilms
Nov 12 '18 at 16:16
add a comment |
4 Answers
4
active
oldest
votes
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 to addEventListener
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 into removeEventListener
works
const anonFunc = () => { console.log("hello"); }
someElem.addEventListener('click', anonFunc);
someElem.removeEventListener('click', anonFunc); // same arguments
does not work
someElem.addEventListener('click', () => { console.log("hello"); });
someElem.removeEventListener('click', ???) // you don't have a reference
// to the anon function so you
// can't pass the correct arguments
// to remove the listener
your choices are
- don't use anonymous or arrow functions
- use a wrappers that will track the arguments for you
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
function ListenerManager() {
let listeners = {};
let nextId = 1;
// Returns an id for the listener. This is easier IMO than
// the normal remove listener which requires the same arguments as addListener
this.on = (elem, ...args) => {
(elem.addEventListener || elem.on || elem.addListener).call(elem, ...args);
const id = nextId++;
listeners[id] = {
elem: elem,
args: args,
};
if (args.length < 2) {
throw new Error('too few args');
}
return id;
};
this.remove = (id) => {
const listener = listeners[id];
if (listener) {
delete listener[id];
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
}
};
this.removeAll = () => {
const old = listeners;
listeners = {};
Object.keys(old).forEach((id) => {
const listener = old[id];
if (listener.args < 2) {
throw new Error('too few args');
}
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
});
};
}
Usage would be something like
const lm = new ListenerManager();
lm.on(saveElem, 'click', handleSave);
lm.on(newElem, 'click', handleNew);
lm.on(plusElem, 'ciick', handlePlusOne);
const id = lm.on(rangeElem, 'input', handleRangeChange);
lm.remove(id); // remove the input event on rangeElem
lm.removeAll(); // remove events on all elements managed by this ListenerManager
note the code above is ES6 and would have to be changed to support really old browsers but the ideas are the same.
add a comment |
Just use a named function expression:
var el = document.querySelector('#myButton');
el.addEventListener('click', function handler() {
console.log('clicked');
el.removeEventListener('click', handler); //<-- will work
});
For sure that can be wrapped in a function:
function once(selector, evt, callback) {
var el = document.querySelector(selector);
el.addEventListener(evt, function handler() {
callback();
el.removeEventListener(evt, handler); //<-- will work
});
}
once("#myButton", "clicl", () => {
// do stuff
});
1
@intervalua my answer still holds true, at least the first part.
– Jonas Wilms
Nov 12 '18 at 16:49
add a comment |
You can use the once
option of EventTarget.addEventListener()
:
Note: supported by all browsers but IE.
var el = document.querySelector('#myButton');
el.addEventListener('click', () => {
console.log('clicked');
}, { once: true });
<button id="myButton">Click</button>
1
Your examples say otherwise. I've got a feeling that you're trying to market your own answer. I'm sorry for wasting your time :)
– Ori Drori
Nov 12 '18 at 16:45
add a comment |
There is an easy solution using closures.
By moving the code to both addEventListener
and removeEventListener
into a single function you can accomplish the task easily:
function ael(el, evt, cb, options) {
console.log('Adding', evt, 'event listener for', el.outerHTML);
el.addEventListener(evt, cb, options);
return function() {
console.log('Removing', evt, 'event listener for', el.outerHTML);
el.removeEventListener(evt, cb, options);
}
}
const btnTest = document.getElementById('test');
let rel = null;
function add() {
if (rel === null) {
rel = ael(btnTest, 'click', () => {
console.info('Clicked on test');
});
}
}
function remove() {
if (typeof rel === 'function') {
rel();
rel = null;
}
}
function removeAll() {
rels.forEach(rel => rel());
}
const rels = [...document.querySelectorAll('[cmd]')].reduce(
(rels, el) => {
const cmd = el.getAttribute('cmd');
if (typeof window[cmd] === 'function') {
rels.push(ael(el, 'click', window[cmd]));
}
return rels;
},
);
<button cmd="add">Add</button>
<button cmd="remove">Remove</button>
<button id="test">Test</button>
<hr/>
<button cmd="removeAll">Remove All</button>
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 call ael
it calls addEventListener
and then returns a function that will call removeEventListener
. 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:
const ael6 = (el, evt, cb, options) => (el.addEventListener(evt, cb, options), () => el.removeEventListener(evt, cb, options));
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53266000%2fhow-can-i-remove-an-event-listener-no-matter-how-the-callback-is-defined%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
4 Answers
4
active
oldest
votes
4 Answers
4
active
oldest
votes
active
oldest
votes
active
oldest
votes
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 to addEventListener
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 into removeEventListener
works
const anonFunc = () => { console.log("hello"); }
someElem.addEventListener('click', anonFunc);
someElem.removeEventListener('click', anonFunc); // same arguments
does not work
someElem.addEventListener('click', () => { console.log("hello"); });
someElem.removeEventListener('click', ???) // you don't have a reference
// to the anon function so you
// can't pass the correct arguments
// to remove the listener
your choices are
- don't use anonymous or arrow functions
- use a wrappers that will track the arguments for you
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
function ListenerManager() {
let listeners = {};
let nextId = 1;
// Returns an id for the listener. This is easier IMO than
// the normal remove listener which requires the same arguments as addListener
this.on = (elem, ...args) => {
(elem.addEventListener || elem.on || elem.addListener).call(elem, ...args);
const id = nextId++;
listeners[id] = {
elem: elem,
args: args,
};
if (args.length < 2) {
throw new Error('too few args');
}
return id;
};
this.remove = (id) => {
const listener = listeners[id];
if (listener) {
delete listener[id];
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
}
};
this.removeAll = () => {
const old = listeners;
listeners = {};
Object.keys(old).forEach((id) => {
const listener = old[id];
if (listener.args < 2) {
throw new Error('too few args');
}
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
});
};
}
Usage would be something like
const lm = new ListenerManager();
lm.on(saveElem, 'click', handleSave);
lm.on(newElem, 'click', handleNew);
lm.on(plusElem, 'ciick', handlePlusOne);
const id = lm.on(rangeElem, 'input', handleRangeChange);
lm.remove(id); // remove the input event on rangeElem
lm.removeAll(); // remove events on all elements managed by this ListenerManager
note the code above is ES6 and would have to be changed to support really old browsers but the ideas are the same.
add a comment |
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 to addEventListener
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 into removeEventListener
works
const anonFunc = () => { console.log("hello"); }
someElem.addEventListener('click', anonFunc);
someElem.removeEventListener('click', anonFunc); // same arguments
does not work
someElem.addEventListener('click', () => { console.log("hello"); });
someElem.removeEventListener('click', ???) // you don't have a reference
// to the anon function so you
// can't pass the correct arguments
// to remove the listener
your choices are
- don't use anonymous or arrow functions
- use a wrappers that will track the arguments for you
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
function ListenerManager() {
let listeners = {};
let nextId = 1;
// Returns an id for the listener. This is easier IMO than
// the normal remove listener which requires the same arguments as addListener
this.on = (elem, ...args) => {
(elem.addEventListener || elem.on || elem.addListener).call(elem, ...args);
const id = nextId++;
listeners[id] = {
elem: elem,
args: args,
};
if (args.length < 2) {
throw new Error('too few args');
}
return id;
};
this.remove = (id) => {
const listener = listeners[id];
if (listener) {
delete listener[id];
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
}
};
this.removeAll = () => {
const old = listeners;
listeners = {};
Object.keys(old).forEach((id) => {
const listener = old[id];
if (listener.args < 2) {
throw new Error('too few args');
}
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
});
};
}
Usage would be something like
const lm = new ListenerManager();
lm.on(saveElem, 'click', handleSave);
lm.on(newElem, 'click', handleNew);
lm.on(plusElem, 'ciick', handlePlusOne);
const id = lm.on(rangeElem, 'input', handleRangeChange);
lm.remove(id); // remove the input event on rangeElem
lm.removeAll(); // remove events on all elements managed by this ListenerManager
note the code above is ES6 and would have to be changed to support really old browsers but the ideas are the same.
add a comment |
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 to addEventListener
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 into removeEventListener
works
const anonFunc = () => { console.log("hello"); }
someElem.addEventListener('click', anonFunc);
someElem.removeEventListener('click', anonFunc); // same arguments
does not work
someElem.addEventListener('click', () => { console.log("hello"); });
someElem.removeEventListener('click', ???) // you don't have a reference
// to the anon function so you
// can't pass the correct arguments
// to remove the listener
your choices are
- don't use anonymous or arrow functions
- use a wrappers that will track the arguments for you
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
function ListenerManager() {
let listeners = {};
let nextId = 1;
// Returns an id for the listener. This is easier IMO than
// the normal remove listener which requires the same arguments as addListener
this.on = (elem, ...args) => {
(elem.addEventListener || elem.on || elem.addListener).call(elem, ...args);
const id = nextId++;
listeners[id] = {
elem: elem,
args: args,
};
if (args.length < 2) {
throw new Error('too few args');
}
return id;
};
this.remove = (id) => {
const listener = listeners[id];
if (listener) {
delete listener[id];
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
}
};
this.removeAll = () => {
const old = listeners;
listeners = {};
Object.keys(old).forEach((id) => {
const listener = old[id];
if (listener.args < 2) {
throw new Error('too few args');
}
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
});
};
}
Usage would be something like
const lm = new ListenerManager();
lm.on(saveElem, 'click', handleSave);
lm.on(newElem, 'click', handleNew);
lm.on(plusElem, 'ciick', handlePlusOne);
const id = lm.on(rangeElem, 'input', handleRangeChange);
lm.remove(id); // remove the input event on rangeElem
lm.removeAll(); // remove events on all elements managed by this ListenerManager
note the code above is ES6 and would have to be changed to support really old browsers but the ideas are the same.
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 to addEventListener
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 into removeEventListener
works
const anonFunc = () => { console.log("hello"); }
someElem.addEventListener('click', anonFunc);
someElem.removeEventListener('click', anonFunc); // same arguments
does not work
someElem.addEventListener('click', () => { console.log("hello"); });
someElem.removeEventListener('click', ???) // you don't have a reference
// to the anon function so you
// can't pass the correct arguments
// to remove the listener
your choices are
- don't use anonymous or arrow functions
- use a wrappers that will track the arguments for you
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
function ListenerManager() {
let listeners = {};
let nextId = 1;
// Returns an id for the listener. This is easier IMO than
// the normal remove listener which requires the same arguments as addListener
this.on = (elem, ...args) => {
(elem.addEventListener || elem.on || elem.addListener).call(elem, ...args);
const id = nextId++;
listeners[id] = {
elem: elem,
args: args,
};
if (args.length < 2) {
throw new Error('too few args');
}
return id;
};
this.remove = (id) => {
const listener = listeners[id];
if (listener) {
delete listener[id];
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
}
};
this.removeAll = () => {
const old = listeners;
listeners = {};
Object.keys(old).forEach((id) => {
const listener = old[id];
if (listener.args < 2) {
throw new Error('too few args');
}
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
});
};
}
Usage would be something like
const lm = new ListenerManager();
lm.on(saveElem, 'click', handleSave);
lm.on(newElem, 'click', handleNew);
lm.on(plusElem, 'ciick', handlePlusOne);
const id = lm.on(rangeElem, 'input', handleRangeChange);
lm.remove(id); // remove the input event on rangeElem
lm.removeAll(); // remove events on all elements managed by this ListenerManager
note the code above is ES6 and would have to be changed to support really old browsers but the ideas are the same.
function ListenerManager() {
let listeners = {};
let nextId = 1;
// Returns an id for the listener. This is easier IMO than
// the normal remove listener which requires the same arguments as addListener
this.on = (elem, ...args) => {
(elem.addEventListener || elem.on || elem.addListener).call(elem, ...args);
const id = nextId++;
listeners[id] = {
elem: elem,
args: args,
};
if (args.length < 2) {
throw new Error('too few args');
}
return id;
};
this.remove = (id) => {
const listener = listeners[id];
if (listener) {
delete listener[id];
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
}
};
this.removeAll = () => {
const old = listeners;
listeners = {};
Object.keys(old).forEach((id) => {
const listener = old[id];
if (listener.args < 2) {
throw new Error('too few args');
}
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
});
};
}
function ListenerManager() {
let listeners = {};
let nextId = 1;
// Returns an id for the listener. This is easier IMO than
// the normal remove listener which requires the same arguments as addListener
this.on = (elem, ...args) => {
(elem.addEventListener || elem.on || elem.addListener).call(elem, ...args);
const id = nextId++;
listeners[id] = {
elem: elem,
args: args,
};
if (args.length < 2) {
throw new Error('too few args');
}
return id;
};
this.remove = (id) => {
const listener = listeners[id];
if (listener) {
delete listener[id];
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
}
};
this.removeAll = () => {
const old = listeners;
listeners = {};
Object.keys(old).forEach((id) => {
const listener = old[id];
if (listener.args < 2) {
throw new Error('too few args');
}
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
});
};
}
answered Nov 12 '18 at 17:22
gmangman
46.8k17112201
46.8k17112201
add a comment |
add a comment |
Just use a named function expression:
var el = document.querySelector('#myButton');
el.addEventListener('click', function handler() {
console.log('clicked');
el.removeEventListener('click', handler); //<-- will work
});
For sure that can be wrapped in a function:
function once(selector, evt, callback) {
var el = document.querySelector(selector);
el.addEventListener(evt, function handler() {
callback();
el.removeEventListener(evt, handler); //<-- will work
});
}
once("#myButton", "clicl", () => {
// do stuff
});
1
@intervalua my answer still holds true, at least the first part.
– Jonas Wilms
Nov 12 '18 at 16:49
add a comment |
Just use a named function expression:
var el = document.querySelector('#myButton');
el.addEventListener('click', function handler() {
console.log('clicked');
el.removeEventListener('click', handler); //<-- will work
});
For sure that can be wrapped in a function:
function once(selector, evt, callback) {
var el = document.querySelector(selector);
el.addEventListener(evt, function handler() {
callback();
el.removeEventListener(evt, handler); //<-- will work
});
}
once("#myButton", "clicl", () => {
// do stuff
});
1
@intervalua my answer still holds true, at least the first part.
– Jonas Wilms
Nov 12 '18 at 16:49
add a comment |
Just use a named function expression:
var el = document.querySelector('#myButton');
el.addEventListener('click', function handler() {
console.log('clicked');
el.removeEventListener('click', handler); //<-- will work
});
For sure that can be wrapped in a function:
function once(selector, evt, callback) {
var el = document.querySelector(selector);
el.addEventListener(evt, function handler() {
callback();
el.removeEventListener(evt, handler); //<-- will work
});
}
once("#myButton", "clicl", () => {
// do stuff
});
Just use a named function expression:
var el = document.querySelector('#myButton');
el.addEventListener('click', function handler() {
console.log('clicked');
el.removeEventListener('click', handler); //<-- will work
});
For sure that can be wrapped in a function:
function once(selector, evt, callback) {
var el = document.querySelector(selector);
el.addEventListener(evt, function handler() {
callback();
el.removeEventListener(evt, handler); //<-- will work
});
}
once("#myButton", "clicl", () => {
// do stuff
});
answered Nov 12 '18 at 16:17
Jonas WilmsJonas Wilms
56.3k42851
56.3k42851
1
@intervalua my answer still holds true, at least the first part.
– Jonas Wilms
Nov 12 '18 at 16:49
add a comment |
1
@intervalua my answer still holds true, at least the first part.
– Jonas Wilms
Nov 12 '18 at 16:49
1
1
@intervalua my answer still holds true, at least the first part.
– Jonas Wilms
Nov 12 '18 at 16:49
@intervalua my answer still holds true, at least the first part.
– Jonas Wilms
Nov 12 '18 at 16:49
add a comment |
You can use the once
option of EventTarget.addEventListener()
:
Note: supported by all browsers but IE.
var el = document.querySelector('#myButton');
el.addEventListener('click', () => {
console.log('clicked');
}, { once: true });
<button id="myButton">Click</button>
1
Your examples say otherwise. I've got a feeling that you're trying to market your own answer. I'm sorry for wasting your time :)
– Ori Drori
Nov 12 '18 at 16:45
add a comment |
You can use the once
option of EventTarget.addEventListener()
:
Note: supported by all browsers but IE.
var el = document.querySelector('#myButton');
el.addEventListener('click', () => {
console.log('clicked');
}, { once: true });
<button id="myButton">Click</button>
1
Your examples say otherwise. I've got a feeling that you're trying to market your own answer. I'm sorry for wasting your time :)
– Ori Drori
Nov 12 '18 at 16:45
add a comment |
You can use the once
option of EventTarget.addEventListener()
:
Note: supported by all browsers but IE.
var el = document.querySelector('#myButton');
el.addEventListener('click', () => {
console.log('clicked');
}, { once: true });
<button id="myButton">Click</button>
You can use the once
option of EventTarget.addEventListener()
:
Note: supported by all browsers but IE.
var el = document.querySelector('#myButton');
el.addEventListener('click', () => {
console.log('clicked');
}, { once: true });
<button id="myButton">Click</button>
var el = document.querySelector('#myButton');
el.addEventListener('click', () => {
console.log('clicked');
}, { once: true });
<button id="myButton">Click</button>
var el = document.querySelector('#myButton');
el.addEventListener('click', () => {
console.log('clicked');
}, { once: true });
<button id="myButton">Click</button>
answered Nov 12 '18 at 16:21
Ori DroriOri Drori
75.5k138092
75.5k138092
1
Your examples say otherwise. I've got a feeling that you're trying to market your own answer. I'm sorry for wasting your time :)
– Ori Drori
Nov 12 '18 at 16:45
add a comment |
1
Your examples say otherwise. I've got a feeling that you're trying to market your own answer. I'm sorry for wasting your time :)
– Ori Drori
Nov 12 '18 at 16:45
1
1
Your examples say otherwise. I've got a feeling that you're trying to market your own answer. I'm sorry for wasting your time :)
– Ori Drori
Nov 12 '18 at 16:45
Your examples say otherwise. I've got a feeling that you're trying to market your own answer. I'm sorry for wasting your time :)
– Ori Drori
Nov 12 '18 at 16:45
add a comment |
There is an easy solution using closures.
By moving the code to both addEventListener
and removeEventListener
into a single function you can accomplish the task easily:
function ael(el, evt, cb, options) {
console.log('Adding', evt, 'event listener for', el.outerHTML);
el.addEventListener(evt, cb, options);
return function() {
console.log('Removing', evt, 'event listener for', el.outerHTML);
el.removeEventListener(evt, cb, options);
}
}
const btnTest = document.getElementById('test');
let rel = null;
function add() {
if (rel === null) {
rel = ael(btnTest, 'click', () => {
console.info('Clicked on test');
});
}
}
function remove() {
if (typeof rel === 'function') {
rel();
rel = null;
}
}
function removeAll() {
rels.forEach(rel => rel());
}
const rels = [...document.querySelectorAll('[cmd]')].reduce(
(rels, el) => {
const cmd = el.getAttribute('cmd');
if (typeof window[cmd] === 'function') {
rels.push(ael(el, 'click', window[cmd]));
}
return rels;
},
);
<button cmd="add">Add</button>
<button cmd="remove">Remove</button>
<button id="test">Test</button>
<hr/>
<button cmd="removeAll">Remove All</button>
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 call ael
it calls addEventListener
and then returns a function that will call removeEventListener
. 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:
const ael6 = (el, evt, cb, options) => (el.addEventListener(evt, cb, options), () => el.removeEventListener(evt, cb, options));
add a comment |
There is an easy solution using closures.
By moving the code to both addEventListener
and removeEventListener
into a single function you can accomplish the task easily:
function ael(el, evt, cb, options) {
console.log('Adding', evt, 'event listener for', el.outerHTML);
el.addEventListener(evt, cb, options);
return function() {
console.log('Removing', evt, 'event listener for', el.outerHTML);
el.removeEventListener(evt, cb, options);
}
}
const btnTest = document.getElementById('test');
let rel = null;
function add() {
if (rel === null) {
rel = ael(btnTest, 'click', () => {
console.info('Clicked on test');
});
}
}
function remove() {
if (typeof rel === 'function') {
rel();
rel = null;
}
}
function removeAll() {
rels.forEach(rel => rel());
}
const rels = [...document.querySelectorAll('[cmd]')].reduce(
(rels, el) => {
const cmd = el.getAttribute('cmd');
if (typeof window[cmd] === 'function') {
rels.push(ael(el, 'click', window[cmd]));
}
return rels;
},
);
<button cmd="add">Add</button>
<button cmd="remove">Remove</button>
<button id="test">Test</button>
<hr/>
<button cmd="removeAll">Remove All</button>
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 call ael
it calls addEventListener
and then returns a function that will call removeEventListener
. 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:
const ael6 = (el, evt, cb, options) => (el.addEventListener(evt, cb, options), () => el.removeEventListener(evt, cb, options));
add a comment |
There is an easy solution using closures.
By moving the code to both addEventListener
and removeEventListener
into a single function you can accomplish the task easily:
function ael(el, evt, cb, options) {
console.log('Adding', evt, 'event listener for', el.outerHTML);
el.addEventListener(evt, cb, options);
return function() {
console.log('Removing', evt, 'event listener for', el.outerHTML);
el.removeEventListener(evt, cb, options);
}
}
const btnTest = document.getElementById('test');
let rel = null;
function add() {
if (rel === null) {
rel = ael(btnTest, 'click', () => {
console.info('Clicked on test');
});
}
}
function remove() {
if (typeof rel === 'function') {
rel();
rel = null;
}
}
function removeAll() {
rels.forEach(rel => rel());
}
const rels = [...document.querySelectorAll('[cmd]')].reduce(
(rels, el) => {
const cmd = el.getAttribute('cmd');
if (typeof window[cmd] === 'function') {
rels.push(ael(el, 'click', window[cmd]));
}
return rels;
},
);
<button cmd="add">Add</button>
<button cmd="remove">Remove</button>
<button id="test">Test</button>
<hr/>
<button cmd="removeAll">Remove All</button>
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 call ael
it calls addEventListener
and then returns a function that will call removeEventListener
. 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:
const ael6 = (el, evt, cb, options) => (el.addEventListener(evt, cb, options), () => el.removeEventListener(evt, cb, options));
There is an easy solution using closures.
By moving the code to both addEventListener
and removeEventListener
into a single function you can accomplish the task easily:
function ael(el, evt, cb, options) {
console.log('Adding', evt, 'event listener for', el.outerHTML);
el.addEventListener(evt, cb, options);
return function() {
console.log('Removing', evt, 'event listener for', el.outerHTML);
el.removeEventListener(evt, cb, options);
}
}
const btnTest = document.getElementById('test');
let rel = null;
function add() {
if (rel === null) {
rel = ael(btnTest, 'click', () => {
console.info('Clicked on test');
});
}
}
function remove() {
if (typeof rel === 'function') {
rel();
rel = null;
}
}
function removeAll() {
rels.forEach(rel => rel());
}
const rels = [...document.querySelectorAll('[cmd]')].reduce(
(rels, el) => {
const cmd = el.getAttribute('cmd');
if (typeof window[cmd] === 'function') {
rels.push(ael(el, 'click', window[cmd]));
}
return rels;
},
);
<button cmd="add">Add</button>
<button cmd="remove">Remove</button>
<button id="test">Test</button>
<hr/>
<button cmd="removeAll">Remove All</button>
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 call ael
it calls addEventListener
and then returns a function that will call removeEventListener
. 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:
const ael6 = (el, evt, cb, options) => (el.addEventListener(evt, cb, options), () => el.removeEventListener(evt, cb, options));
function ael(el, evt, cb, options) {
console.log('Adding', evt, 'event listener for', el.outerHTML);
el.addEventListener(evt, cb, options);
return function() {
console.log('Removing', evt, 'event listener for', el.outerHTML);
el.removeEventListener(evt, cb, options);
}
}
const btnTest = document.getElementById('test');
let rel = null;
function add() {
if (rel === null) {
rel = ael(btnTest, 'click', () => {
console.info('Clicked on test');
});
}
}
function remove() {
if (typeof rel === 'function') {
rel();
rel = null;
}
}
function removeAll() {
rels.forEach(rel => rel());
}
const rels = [...document.querySelectorAll('[cmd]')].reduce(
(rels, el) => {
const cmd = el.getAttribute('cmd');
if (typeof window[cmd] === 'function') {
rels.push(ael(el, 'click', window[cmd]));
}
return rels;
},
);
<button cmd="add">Add</button>
<button cmd="remove">Remove</button>
<button id="test">Test</button>
<hr/>
<button cmd="removeAll">Remove All</button>
function ael(el, evt, cb, options) {
console.log('Adding', evt, 'event listener for', el.outerHTML);
el.addEventListener(evt, cb, options);
return function() {
console.log('Removing', evt, 'event listener for', el.outerHTML);
el.removeEventListener(evt, cb, options);
}
}
const btnTest = document.getElementById('test');
let rel = null;
function add() {
if (rel === null) {
rel = ael(btnTest, 'click', () => {
console.info('Clicked on test');
});
}
}
function remove() {
if (typeof rel === 'function') {
rel();
rel = null;
}
}
function removeAll() {
rels.forEach(rel => rel());
}
const rels = [...document.querySelectorAll('[cmd]')].reduce(
(rels, el) => {
const cmd = el.getAttribute('cmd');
if (typeof window[cmd] === 'function') {
rels.push(ael(el, 'click', window[cmd]));
}
return rels;
},
);
<button cmd="add">Add</button>
<button cmd="remove">Remove</button>
<button id="test">Test</button>
<hr/>
<button cmd="removeAll">Remove All</button>
edited Nov 12 '18 at 16:40
answered Nov 12 '18 at 16:10
IntervaliaIntervalia
4,15711032
4,15711032
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53266000%2fhow-can-i-remove-an-event-listener-no-matter-how-the-callback-is-defined%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Or you just use jQuerys
$(sth).once("click", ...)
...– Jonas Wilms
Nov 12 '18 at 16:16