How to removeEventListener that is addEventListener with anonymous function?

JavascriptEvent HandlingAnonymous Function

Javascript Problem Overview


function doSomethingWith(param)
{
    document.body.addEventListener(
        'scroll',
        function()
        {
            document.write(param);
        },
        false
    ); // An event that I want to remove later
}
setTimeout(
    function()
    {
        document.body.removeEventListener('scroll', HANDLER ,false);
            // What HANDLER should I specify to remove the anonymous handler above?
    },
    3000
);
doSomethingWith('Test. ');

Javascript Solutions


Solution 1 - Javascript

You can't. You have to use a named function or store the reference somehow.

var handler;

function doSomethingWith(param) {
    handler = function(){
        document.write(param);
    };  
    document.body.addEventListener('scroll', handler,false);
}
setTimeout(function() {
     document.body.removeEventListener('scroll', handler ,false);
}, 3000);

The best would be to do this in a structured way, so that you can identify different handlers and remove them. In the example above, you obviously could only remove the last handler.

Update:

You could create your own handler handler (:)) :

var Handler = (function(){
    var i = 1,
        listeners = {};

    return {
        addListener: function(element, event, handler, capture) {
            element.addEventListener(event, handler, capture);
            listeners[i] = {element: element, 
                             event: event, 
                             handler: handler, 
                             capture: capture};
            return i++;
        },
        removeListener: function(id) {
            if(id in listeners) {
                var h = listeners[id];
                h.element.removeEventListener(h.event, h.handler, h.capture);
                delete listeners[id];
            }
        }
    };
}());

Then you can use it with:

function doSomethingWith(param) {
    return Handler.addListener(document.body, 'scroll', function() {
        document.write(param);
    }, false);
}

var handler = doSomethingWith('Test. ');

setTimeout(function() {
     Handler.removeListener(handler);
}, 3000);

DEMO

Solution 2 - Javascript

You can't, you need a reference to the function:

function doSomethingWith(param) {
   var fn = function(){ document.write(param); };
   document.body.addEventListener('scroll', fn, false);
   setTimeout(function(){ document.body.removeEventListener('scroll', fn, false); }, 3000);
}
doSomethingWith('Test. ');

Solution 3 - Javascript

You could also do this like that:

const ownAddEventListener = (scope, type, handler, capture) => {
  scope.addEventListener(type, handler, capture);
  return () => {
    scope.removeEventListener(type, handler, capture);    
  }
}

Then you can remove the event listener like this:

// Add event listener
const disposer = ownAddEventListener(document.body, 'scroll', () => { 
  // do something
}, false);

// Remove event listener
disposer();

Solution 4 - Javascript

if you dont have to support IE, you can use the once option

[Element].addEventListener('click', () => {...}, {
  capture: false,
  once: true
});

Solution 5 - Javascript

This significantly improves upon the top @FelixKling answer. Improvements are:

(1) Aligned with MDN docs which refer to type, listener, useCapture

(2) Removes old school IIFE and adopts a class based approach

(3) Added addEventListenerById method which supports using your own custom ID (and avoids the need to retrieve ID from return value)

(4) removeEventListener method returns ID of removed listener or null

(5) Added length method to return the number of active listeners

(6) Added SO code snippet (to prove it works)

Code follows:

class Listeners {

  #listeners = {} // # in a JS class signifies private 
  #idx = 1

  // add event listener, returns integer ID of new listener
  addEventListener(element, type, listener, useCapture = false) {
    this.#privateAddEventListener(element, this.#idx, type, listener, useCapture)
    return this.#idx++
  }
  
  // add event listener with custom ID (avoids need to retrieve return ID since you are providing it yourself)
  addEventListenerById(element, id, type, listener, useCapture = false) {
    this.#privateAddEventListener(element, id, type, listener, useCapture)
    return id
  }

  #privateAddEventListener(element, id, type, listener, useCapture) {
    if (this.#listeners[id]) throw Error(`A listener with id ${id} already exists`)
    element.addEventListener(type, listener, useCapture)
    this.#listeners[id] = {element, type, listener, useCapture}
  }

  // remove event listener with given ID, returns ID of removed listener or null (if listener with given ID does not exist)
  removeEventListener(id) {
    const listen = this.#listeners[id]
    if (listen) {
      listen.element.removeEventListener(listen.type, listen.listener, listen.useCapture)
      delete this.#listeners[id]
    }
    return !!listen ? id : null
  }

  // returns number of events listeners
  length() {
    return Object.keys(this.#listeners).length
  }
}

// For demo purposes only, a button to which the click listeners will be attached
const testBtn = document.querySelector('.test-button')

// Usage
const listeners = new Listeners() // in a modular environment ... export const listeners = new Listeners()

const listener1 = listeners.addEventListener(testBtn, 'click', e => console.log('hello from click listener #1', e.offsetX, e.offsetY))
const listener2 = listeners.addEventListener(testBtn, 'click', e => console.log('hello from click listener #2', e.offsetX, e.offsetY))
listeners.addEventListenerById(testBtn, 'listener-id', 'click', e => console.log('hello from click listener #listener-id', e.offsetX, e.offsetY))

// Click function for the 3 remove listener buttons (not for the testBtn to which the listeners are attached)
const onRemoveClick = (id) => {
  const removed = listeners.removeEventListener(id)
  if (removed == null) {
    console.log(`cannot remove listener #${id} (does not exist)`)
  } else {
    console.log(`ID of removed listener #${removed}`)
  }
  const listenersCount = listeners.length()
  console.log(`there are ${listenersCount} listeners still listening`)
}

.test-button {
  width: 35%;
  height: 40px;
  float: left;
}

.remove-listener-button {
  width: 15%;
  height: 40px;
  float: left;
}

<button class="test-button">click to prove multiple listeners are listening to my click</button>

<button class="remove-listener-button" onclick="onRemoveClick(1)">remove listener #1</button>
<button class="remove-listener-button" onclick="onRemoveClick(2)">remove listener #2</button>
<button class="remove-listener-button" onclick="onRemoveClick('listener-id')">remove #listener-id</button>

FOR TYPESCRIPT LOVERS

Here's the same in TypeScript with some enhancements to the above JS version:

(1) removeEventListener method returns details of the removed listener (including ID) as opposed to only the ID of the removed listener

(2) Added ids method which returns the ids of all active listeners

(3) Added 3 methods addEventListeners addEventListenersByIds removeEventListeners which allow you to add / remove multiple listeners in one call (see usage example below)

(4) Added removeAllEventListeners and destroy methods for cleanup (essentially these 2 are the same but the latter does not return a value)

Here's the TypeScript code (just copy and paste into a new .ts file):

interface IInternalListener {
  element: HTMLElement
  id: string
  type: string
  listener: EventListenerOrEventListenerObject
  useCapture: boolean
}

export interface IListener extends Omit<IInternalListener, 'id'> {
  id: string | number
}

class Listeners {

  #listeners: { [key: string]: IInternalListener } = {}
  #idx = 1 // # in a JS class signifies private

  // add event listener, returns integer ID of new listener
  addEventListener(element: HTMLElement, type: string, listener: EventListenerOrEventListenerObject, useCapture = false): number {
	this.#privateAddEventListener(element, this.#idx.toString(), type, listener, useCapture)
	return this.#idx++
  }

  addEventListeners(element: HTMLElement, types: string[], listener: EventListenerOrEventListenerObject, useCapture = false): number[] {
	const returnIds: number[] = []
	types.forEach((type: string) => {
	  const returnId: number = this.addEventListener(element, type, listener, useCapture)
	  returnIds.push(returnId)
	})
	return returnIds
  }

  // add event listener with custom ID (avoids need to retrieve return ID since you are providing it yourself)
  addEventListenerById(element: HTMLElement, id: string | number, type: string, listener: EventListenerOrEventListenerObject, useCapture = false): string | number { // eslint-disable-line max-len
	return this.#privateAddEventListener(element, id.toString(), type, listener, useCapture)
  }

  addEventListenersByIds(element: HTMLElement, ids: Array<string | number>, types: string[], listener: EventListenerOrEventListenerObject, useCapture = false): Array<string | number> { // eslint-disable-line max-len
	const returnIds: Array<string | number> = []
	if (ids.length !== types.length) throw Error(`Cannot add ${types.length} event listeners using ${ids.length} ids - ids and types must be of equal length`)
	types.forEach((type: string, idx: number) => {
	  const id: string | number = ids[idx]
	  const returnId: string | number = this.addEventListenerById(element, id, type, listener, useCapture)
	  returnIds.push(returnId)
	})
	return returnIds
  }

  // remove event listener with given ID, returns removed listener or null (if listener with given ID does not exist)
  removeEventListener(id: string | number): IListener | null {
	const strId: string = id.toString()
	const internalListener: IInternalListener = this.#listeners[strId]
	if (internalListener) {
	  internalListener.element.removeEventListener(internalListener.type, internalListener.listener, internalListener.useCapture)
	  const listener: IListener = this.#privateGetListener(internalListener)
	  delete this.#listeners[strId]
	  return listener
	}
	return null
  }

  // noinspection JSUnusedGlobalSymbols
  removeEventListeners(ids: Array<string | number>): Array<IListener | null> {
	const returnListeners: Array<IListener | null> = []
	ids.forEach((id: string | number) => {
	  const returnListener: IListener | null = this.removeEventListener(id)
	  returnListeners.push(returnListener)
	})
	return returnListeners
  }

  // removes all event listeners and resets idx
  removeAllEventListeners(): Array<IListener | null> {
	const ids: Array<string | number> = this.ids()
	const returnListeners: Array<IListener | null> = this.removeEventListeners(ids)
	this.#idx = 1
	return returnListeners
  }

  // same as removeAllEventListeners but no return value
  destroy(): void {
	this.removeAllEventListeners()
  }

  // returns ids of events listeners
  ids(): Array<string | number> {
	const ids: string[] = Object.keys(this.#listeners)
	return ids.map(id => this.#privateExternalId(id))
  }

  // returns number of events listeners
  length(): number {
	return Object.keys(this.#listeners).length
  }

  #privateAddEventListener(element: HTMLElement, id: string, type: string, listener: EventListenerOrEventListenerObject, useCapture: boolean): string | number {
	if (this.#listeners[id]) throw Error(`A listener with id ${id} already exists`)
	element.addEventListener(type, listener, useCapture)
	this.#listeners[id] = { id, element, type, listener, useCapture }
	return this.#privateExternalId(id)
  }

  #privateGetListener(listener: IInternalListener): IListener {
	return {
	  ...listener,
	  id: this.#privateExternalId(listener.id)
	}
  }

  #privateExternalId(id: string): string | number {
	const idIsInteger = /^\d+$/.test(id)
	if (idIsInteger) return parseInt(id, 10)
	return id
  }
}

export const listeners: Listeners = new Listeners()

And TypeScript usage:

import { listeners, IListener } from './your-path/listeners'

// Add and remove listener
const listenerId: number = listeners.addEventListener(mainVideo, 'timeupdate', (evt: Event) => console.log('hi', evt))
listeners.removeEventListener(listenerId)

// Add and remove listener by custom ID
listeners.addEventListenerById(mainVideo, 'custom-id', 'timeupdate', (evt: Event) => console.log('hello', evt))
listeners.removeEventListener('custom-id')

// Log id of all active listeners
console.log(listeners.ids())

// Log active listeners count
console.log(listeners.length())

// Get details of removed listener
listeners.addEventListenerById(mainVideo, 'fred', 'timeupdate', (evt: Event) => console.log('bye', evt))
const removedListener: IListener | null = listeners.removeEventListener('fred')
console.log('removed listener was', removedListener)

// clean up
const removedListeners: Array<IListener | null> = listeners.removeAllEventListeners()
console.log('removed listeners were', removedListeners)

// simple quick clean up
listeners.destroy()

Advanced TypeScript usage for adding / removing multiple listeners in one call:

// Add multiple event listeners
const listenerIds: number[] = listeners.addEventListeners(this.video, ['timeupdate', 'seeking', 'pause', 'play', 'playing'], (evt: Event) => {
  const target = evt.target as HTMLVideoElement
  this.currentTime = target.currentTime
})
console.log(listenerIds)

// Add multiple event listeners with custom IDs
listeners.addEventListenersByIds(this.video, ['id-one', 'id-two', 'id-three'], ['timeupdate', 'seeking', 'pause'], (evt: Event) => {
  console.log(evt)
})

// Remove multiple event listeners
const removedListeners: Array<IListener | null> = listeners.removeEventListeners([...listenerIds, 'id-two'])
console.log(removedListeners)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJapboyView Question on Stackoverflow
Solution 1 - JavascriptFelix KlingView Answer on Stackoverflow
Solution 2 - JavascriptdavinView Answer on Stackoverflow
Solution 3 - JavascriptSimon JentschView Answer on Stackoverflow
Solution 4 - JavascriptmanoiView Answer on Stackoverflow
Solution 5 - Javascriptdanday74View Answer on Stackoverflow