How can I remove an event listener no matter how the callback is defined












1















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.










share|improve this question

























  • Or you just use jQuerys $(sth).once("click", ...) ...

    – Jonas Wilms
    Nov 12 '18 at 16:16
















1















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.










share|improve this question

























  • Or you just use jQuerys $(sth).once("click", ...) ...

    – Jonas Wilms
    Nov 12 '18 at 16:16














1












1








1








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.










share|improve this question
















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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



















  • 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












4 Answers
4






active

oldest

votes


















2














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.






share|improve this answer































    1














    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
    });





    share|improve this answer



















    • 1





      @intervalua my answer still holds true, at least the first part.

      – Jonas Wilms
      Nov 12 '18 at 16:49



















    0














    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>








    share|improve this answer



















    • 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





















    0














    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));





    share|improve this answer

























      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
      });


      }
      });














      draft saved

      draft discarded


















      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









      2














      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.






      share|improve this answer




























        2














        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.






        share|improve this answer


























          2












          2








          2







          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.






          share|improve this answer













          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);
          });
          };
          }






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 12 '18 at 17:22









          gmangman

          46.8k17112201




          46.8k17112201

























              1














              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
              });





              share|improve this answer



















              • 1





                @intervalua my answer still holds true, at least the first part.

                – Jonas Wilms
                Nov 12 '18 at 16:49
















              1














              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
              });





              share|improve this answer



















              • 1





                @intervalua my answer still holds true, at least the first part.

                – Jonas Wilms
                Nov 12 '18 at 16:49














              1












              1








              1







              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
              });





              share|improve this answer













              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
              });






              share|improve this answer












              share|improve this answer



              share|improve this answer










              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














              • 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











              0














              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>








              share|improve this answer



















              • 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


















              0














              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>








              share|improve this answer



















              • 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
















              0












              0








              0







              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>








              share|improve this answer













              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>






              share|improve this answer












              share|improve this answer



              share|improve this answer










              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
















              • 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













              0














              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));





              share|improve this answer






























                0














                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));





                share|improve this answer




























                  0












                  0








                  0







                  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));





                  share|improve this answer















                  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>






                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  edited Nov 12 '18 at 16:40

























                  answered Nov 12 '18 at 16:10









                  IntervaliaIntervalia

                  4,15711032




                  4,15711032






























                      draft saved

                      draft discarded




















































                      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.




                      draft saved


                      draft discarded














                      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





















































                      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







                      這個網誌中的熱門文章

                      Xamarin.form Move up view when keyboard appear

                      Post-Redirect-Get with Spring WebFlux and Thymeleaf

                      Anylogic : not able to use stopDelay()