Published at
Updated at
Reading time
3min
This post is part of my Today I learned series in which I share all my web development learnings.

To build interactive web interfaces, you can attach event listeners to DOM events.

Query an element, define an event type, call addEventListener and pair it with a callback function — and violà, you are ready to react to clicks, keypresses, scrolls and many other events.

The following code reacts to button clicks:

document.querySelector('button')
  .addEventListener('click', () => {
    console.log('element clicked');
  });

According to MDN, target.addEventListener defines the following parameters:

target.addEventListener(type, listener [, options]);
target.addEventListener(type, listener [, useCapture]);
target.addEventListener(type, listener [, useCapture, wantsUntrusted  ]); // Gecko/Mozilla only

addEventListener accepts the event type, a listener callback function and an options or useCapture parameter.

To learn more about possible options or useCapture, head over to the MDN addEventListener documentation.

What if I told you that the listener parameter can be a function or an object?

addEventListener and the EventListener interface

MDN documents the listener parameter as follows:

[listener] must be an object implementing the EventListener interface, or a JavaScript function.

The early DOM events specification (we're talking pre-HTML5 here) defined an EventListener interface. Any object that defines a handleEvent method counts as a valid EventListener.

// a class implementing
// the `EventListener` interface
class EventHandler {
  constructor() {
    this.eventCount = 0;
  }

  handleEvent() {
    this.eventCount++;
    console.log(`Event triggered ${this.eventCount} time(s)`);
  }
}

document.querySelector('button')
  .addEventListener('click', new EventHandler());

The code above defines a JavaScript class EventHandler. An initialized event handler object can then be passed to addEventListener to react to specific events. And because it's an object, it can hold state.

The event handler object keeps track of the number of times a specific event occurred (check it on CodePen) by implementing an eventCount field. All information is stored in the object itself, and the code works without outer scope variables.

I like this pattern, and I can see it coming in handy when dealing with sequential events.

According to MDN, the EventListener interface is supported by all major browsers, and you can safely pass objects that implement it to addEventListener.

When would you pass EventListener objects to addEventListener? I'd love to learn about more examples!

Edit: Someone shared the following snippet on Reddit.

class MyComponent {
  constructor (el) {
    this.el = el
    this.el.addEventListener('click', this)
  }
  handleEvent (event) {
    console.log('my component element was clicked')
  }
  destroy () {
    this.el.removeEventListener('click', this)
  }
}

const component = new MyComponent(
  document.querySelector('button')
);

The MyComponent class accepts a DOM element and automatically attaches/detaches event listeners. It also implements a handleEvent method so you can pass this to addEventListener. Fancy!

And as Chris Ferdinandi points out, passing this to addEventListener works very well with web components, too.

Here's a shortened version, showing how handleEvent shines in web components.

customElements.define('count-up', class extends HTMLElement {
  constructor () {
    super();
  }

  handleEvent (event) {
    // handle all the events
  } 

  connectedCallback () {
    // pass `this` to the event listener and forward 
    // triggered events to `handleEvent`
    this.button.addEventListener('click', this);
  }

  disconnectedCallback () {
    // disconnect event handler
    this.button.removeEventListener('click', this);
  }
});

Good stuff!

If you enjoyed this article...

Join 5.5k readers and learn something new every week with Web Weekly.

Web Weekly — Your friendly Web Dev newsletter
Reply to this post and share your thoughts via good old email.
Stefan standing in the park in front of a green background

About Stefan Judis

Frontend nerd with over ten years of experience, freelance dev, "Today I Learned" blogger, conference speaker, and Open Source maintainer.

Related Topics

Related Articles