How to use removeEventListener on a handler which was both directly registered and created on the spot by .bind()?

441 Views Asked by At

So I called a function with .bind() but later in my code I have to remove that eventListner.

EventTarget.addEventListener("click", myFunction.bind(null, arg))

2

There are 2 best solutions below

0
Mike 'Pomax' Kamermans On

No reason to use .bind for this use-case, instead use a modern AbortController signal:

const controller = new AbortController();

EventTarget.addEventListener(
  `click`,
  () => blah.handler(arg),
  {
    signal: controller.signal
  }
);

And then whenever you need to clean up that event listener:

controller.abort();

And done. The JS engine will know what to do, no need for removeEventListener with the same arguments as you used for addEventListener.

1
Peter Seliger On

One could store/set each element specific bound handler function in/to a WeakMap by the element's reference itself, thus one easily can retrieve/get it again and e.g. un-register it.

function logEventWithBoundHandlerId({ type, currentTarget }) {
  console.log({ type, currentTarget, bounId: this.id });
}
const handlerStorage = new WeakMap;

function registerAnyBoundHandler({ currentTarget }) {
  document
    .querySelectorAll('div button')
    .forEach((elm, idx) => {

      // create bound handler.
      const handler = logEventWithBoundHandlerId.bind({ id: idx + 1 });

      // add bound handler to storage (weak map)
      // by element reference.
      handlerStorage.set(elm, handler);

      // add listener.
      elm.addEventListener('click', handler);
    });
  currentTarget.parentNode.querySelector('[disabled]').disabled = false;
  currentTarget.disabled = true;
}
function unregisterAnyBoundHandler({ currentTarget }) {
  console.clear();

  document
    .querySelectorAll('div button')
    .forEach(elm => {

      // remove listener.
      elm.removeEventListener('click', handlerStorage.get(elm));

      // delete bound handler from storage (weak map)
      // by element reference.
      handlerStorage.delete(elm);
    });
  currentTarget.parentNode.querySelector('[disabled]').disabled = false;
  currentTarget.disabled = true;
}

function init() {
  const [ btnRegister, btnUnregister ] = document
    .querySelectorAll('fieldset button');

  btnRegister.addEventListener('click', registerAnyBoundHandler);
  btnUnregister.addEventListener('click', unregisterAnyBoundHandler);
}
init();
body { margin: 0; }
fieldset { margin: 0 0 5px 0; padding: 5px 3px; }
div { padding-left: 4px; }
.as-console-wrapper { max-height: 65%!important; top: auto; }
<fieldset>
  <button>register any bound handler</button>
  <button disabled>unregister any bound handler</button>
</fieldset>

<div>
  <button>click 1</button>
  <button>click 2</button>
  <button>click 3</button>
</div>

The same storage approach has to be followed in case one chooses to utilize the AbortController and AbortSignal Web APIs.

function logEventWithBoundHandlerId({ type, currentTarget }) {
  console.log({ type, currentTarget, bounId: this.id });
}
const controllerStorage = new WeakMap;

function registerAnyBoundHandler({ currentTarget }) {
  document
    .querySelectorAll('div button')
    .forEach((elm, idx) => {

      // create element specific abort controller.
      const controller = new AbortController;

      // add element specific controller to storage
      // (weak map) by the very element's reference.
      controllerStorage.set(elm, controller);

      // add listener with the `signal` option.
      elm.addEventListener(
        'click',
        logEventWithBoundHandlerId.bind({ id: idx + 1 }),
        { signal: controller.signal },
      );
    });
  currentTarget.parentNode.querySelector('[disabled]').disabled = false;
  currentTarget.disabled = true;
}
function unregisterAnyBoundHandler({ currentTarget }) {
  console.clear();

  document
    .querySelectorAll('div button')
    .forEach(elm => {

      // retrieve element specific controller.
      const controller = controllerStorage.get(elm);
      // abort controller.
      controller.abort();

      // delete element specific controller from
      // storage (weak map) by element reference.
      controllerStorage.delete(elm);
    });
  currentTarget.parentNode.querySelector('[disabled]').disabled = false;
  currentTarget.disabled = true;
}

function init() {
  const [ btnRegister, btnUnregister ] = document
    .querySelectorAll('fieldset button');

  btnRegister.addEventListener('click', registerAnyBoundHandler);
  btnUnregister.addEventListener('click', unregisterAnyBoundHandler);
}
init();
body { margin: 0; }
fieldset { margin: 0 0 5px 0; padding: 5px 3px; }
div { padding-left: 4px; }
.as-console-wrapper { max-height: 65%!important; top: auto; }
<fieldset>
  <button>register any bound handler</button>
  <button disabled>unregister any bound handler</button>
</fieldset>

<div>
  <button>click 1</button>
  <button>click 2</button>
  <button>click 3</button>
</div>