27

I always wondered how clean is such approach - to remove an event listener from within that very listener.

UPDATE:

Internally I keep a hash of objects and listeners, so I potentially can remove event listener from any place. I'm just concerned of removing it from within itself. Will such action do a job actually?

UPDATE

I'm asking about addEventListener, removeEventListener stuff.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
jayarjo
  • 16,124
  • 24
  • 94
  • 138

7 Answers7

61

You can pass the once option to have a listener act only once, then remove itself. Docs: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters

Example:

  element.addEventListener('eventname', (ev) => {
    console.log("event is captured only once.");
    // do more stuff...
  }, { once: true });

From the same docs link above, modern browser support is good, but is not available for Internet Explorer.

broc.seib
  • 21,643
  • 8
  • 63
  • 62
  • 1
    Yeah... I don't think that was available back when I was asking the original question :D – jayarjo Aug 23 '19 at 17:03
  • But I think simply having a named event handler and then actually removing it from within the handler is the best and the most cross-browser compliant approach. – jayarjo Aug 23 '19 at 17:06
  • 1
    Also, in some applications once is not enough -- "once" is just a specific case. – broc.seib Aug 23 '19 at 21:14
  • I think this is the best way in 2020 for most purposes, given that the reason most people want to remove a listener is to have it only execute once. However, in situations where the condition for removal is not singleton execution, this solution is not so great. I guess you can still make it work by setting `once` and adding another listener in the callback function if an arbitrary condition is met, in essense doing some kind of weird recursion, but at that point other solutions are clearly cleaner. – cyqsimon May 27 '20 at 06:12
16

I just saw this because i wondered the exact same question!

arguments.callee is your friend...

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments/callee

so you'd have

blah.addEventListener('click',function(e){
    e.source.removeEventListener('click', arguments.callee);
    blee bloo bleep
});

this works in Titanium Appcelerator, so it should work in javascript too (because they are The Same Thing Kinda)

NB do NOT add () to the end of arguments.callee in this example, unless you like seeing... bah dum tish!.

In fact, if you don't want to use arguments.callee, this also might work (untested):

blah.addEventListener('click', anyThingYouWantHere = function(e){
    e.source.removeEventListener('click', anyThingYouWantHere);
    blee bloo bleep
});

Where "anythingYouWantHere" is any variable name you'd like ~ you're effectively "naming" the function as you add it.

bharal
  • 15,461
  • 36
  • 117
  • 195
  • 3
    That's not allowed in ES5 strict mode, isn't it? – J. K. Oct 25 '12 at 20:13
  • Hah! I never even *knew* such a thing existed. ANYhoo, i had a poke around and found this http://stackoverflow.com/a/1335595/1061426 which seems to indicate it is still around. Also, the link i referenced for arguments.callee doesn't mention it is deprecated either. The link *does* mention the `()` variant won't work, but we're not using that here... on account of stackoverflow – bharal Oct 25 '12 at 21:14
  • Hi! your second exemple is an assignment, it will work for javascript with the assignment operator '=' instead of ':' – Drozerah Mar 11 '20 at 18:41
  • @user see the comment above yours – bharal Mar 15 '21 at 23:47
11

I just made a wrapper function that generates a self destructing event listener:

let addSelfDestructingEventListener = (element, eventType, callback) => {
    let handler = () => {
        callback();
        element.removeEventListener(eventType, handler);
    };
    element.addEventListener(eventType, handler);
};

So far it works great :)

JCLaHoot
  • 1,034
  • 2
  • 13
  • 21
  • 1
    This works really well, not all browsers support {once: true}!! this should be the recommended answer. – Peter I Jul 09 '20 at 10:03
3

@bharal answer gave me now this solution:

//example
addBlurListener(element, field) {
    const listenToBlur = (e) => {
        e.target.removeEventListener(e.type, listenToBlur);
        //your stuff
    };
    element.addEventListener('blur', listenToBlur);
},
Philipp Mochine
  • 4,351
  • 10
  • 36
  • 68
1

You could try something like this, depending on how it's called:

some_div.onclick = function () {
    ...
    this.onclick = null;
    // or: some_div.onclick = null;
};

Or is it event listeners you're concerned with? Because those are a little bit more complicated.

sdleihssirhc
  • 42,000
  • 6
  • 53
  • 67
1

If you want listener only trigger once, you can use this code:

element.addEventListener('eventname', function callback(){}, { once: true }); 

Or use wrapper to do the same thing:

function addOneTimeEventListener(element, event, callback) {
    const wrapper = e => {
        try {callback(e)} finally {
            element.removeEventListener(event, wrapper);
        };
    }
    element.addEventListener(event, wrapper);
}

// example
addOneTimeEventListener(document.body, 'click', e => {
  console.log('this message only show once.');
});

If you want to decide when to remove listener:

function addEventListener(element, event, callback) {
    const wrapper = e => {
        callback(e, () => element.removeEventListener(event, wrapper));
    }
    element.addEventListener(event, wrapper);
}

// example
let count = 0;
addEventListener(document.body, 'click', (e, closeListener) => {
  console.log(`click:${++count}`);
  if(count == 3) closeListener();
});
0

If you use jQuery, you will probably have some convenience methods exposed for interacting with event handlers -- see bind()/unbind(), delegate()/undelegate(), one(), and similar methods.

I don't have much experience with other frameworks, but I'd imagine they offer similar functionality. If you're not using a framework at all, @sdleihssirhc has an acceptable answer.

EDIT: Ah, perhaps you're looking for something more like addEventListener() and removeEventListener(). Again, a framework will offer some convenience to your interactions and save you the trouble of reinventing the wheel in some cases.

Cyranix
  • 337
  • 1
  • 4
  • 11