Four ways of listening to DOM events in Angular (Part 1: Event Binding)

Listening to DOM events and keeping Angular application performant can be challenging. Angular offers several methods to listen to events. By examining those available options and methods, we can understand and be aware of the characteristics that each one has. The four options for listening and reacting to DOM events that will be covered in this blog series are:

  1. Event Binding: One-way data binding, in which information is sent from a component’s template to the component’s class
  2. @HostListener: Angular decorator that handles events on the host element
  3. Renderer2: Using Renderer2 .listen() method for a target event and element
  4. RxJS: Using RxJS .fromEvent() operator that turns events into observable sequences
Give a fool a hammer and everything becomes a nail.

There is no such tool that works for all of the use cases. Listening to DOM events is no different. In this series, as we delve into those four options, keep that in mind because each of the methods we look into has its own benefits and trade-offs. But for this blog post, let’s dive into Angular event binding.


Event Binding

In event binding, information flows from elements in a component template to the corresponding component’s class. With event binding, you don’t need to give the target element an identifier in order to access and attach your listeners to it because you are dealing with the target event and the target element directly in the template.

So it is the simplest way of listening to events on elements in a component template. Let’s look at a simple example of event binding:

 <button (click)="handleClick()">Save</button>

As shown in the example above, you place your target event name within parenthesis on the left of an equal sign and your event handler within quotes on the right of an equal sign. You can also bind an unlimited number of event handlers on the same event by separating them with a semi-colon:

<button (click)="handleClick1(); handleClick2(); …">Save</button>

If you need access to the event payload object, you can pass $event as an argument to your handler function:

<button (click)="handleClick($event)">Save</button>

Just like in the example above, make sure it’s passed exactly as "$event”. Angular recognizes this and passes in the event payload that is the native event object emitted by the browser. If you pass it incorrectly (e.g., missing the $ sign), you will receive undefined in your handler function. If you need something specific from the payload object, you can pass it directly into the handler function with one of the event properties:

<button (click)="handleClick($event.target)">Save</button>

There is another peculiar Angular event feature in relation to keyup and keydown events. If you only need to listen to specific keys, Angular offers shortcuts for that. For example, in order to listen to only the ENTER key, you can specify that key as a property of keyup or keydown events: keyup.enter or keydown.enter. These specific key events are called pseudo-events. You can use pseudo-events with event binding:

<input (keydown.enter)="onEnterKey($event)">

We can even listen to key combinations:

<input (keyup.control.shift.enter)="onCtrlShiftEnter($event)">

You can see how easy Angular makes it listen to complex key combinations. However, currently Angular lacks elaborate documentation on pseudo-events. So I have written another brief blog post on Angular pseudo-events.

Furthermore, event binding is bound to a component template. This means you cannot listen to events fired outside the components template. Another limitation to event binding is that you cannot listen to events on your component’s host element, which wraps the component’s template. In the next post of this series, I will cover @HostListener which enables you to listen to events on the host itself.

But every rule has an exception. Even though you cannot listen to events outside of the component’s template, you can listen to events on global elements such as window, document, and body with event binding.

<button (document:click)="handleClick()">Save</button>

In this example, the "handleClick()" function responds to clicks on both the button and on the entire document. However, listening to events on the global elements such as window or document is not recommended because it limits the reusability of the components themselves. If you have multiple instances of a component that handles one of the global events then all instances will simultaneously try to respond to the event after it is fired. This has a direct impact on the performance of your components and the application as a whole.

Something else a normal Angular event binding can handle are events that bubble up to a parent component from a child component. If a DOM event is fired in the child, the parent component will hear it as the event propagates up through. One thing to note is that only DOM events bubble up like this. Custom Angular events fired by the EventEmitter do not bubble up. Also, keep in mind that custom Angular events are different from custom DOM events, which bubble up just like regular DOM events.

Finally, the last thing to note about event binding is that you cannot dynamically add or remove the listener once event binding is set on an element in the component template. The bindings will be active once the element gets rendered into the DOM, and remain active until it’s removed from the DOM. This means, for event binding, it is Angular that controls when a listener starts or stops listening to their target events. Later in the series, we will look at using Renderer2 and RxJS to dynamically add and remove event listeners only if and when specific conditions are met. This will enable us to create components and directives that handle and manipulate events in complex ways.


Let’s Summarize It — Key Takeaways:

  • Event binding is the simplest (or the most proper) way of listening to events in a component template.
  • With event binding, you cannot listen to events fired outside a component’s template with the exceptions of global elements such as window, document, and body.
  • Listening to global events in a reusable component is not recommended as it could potentially result in poor performance.
  • Parent component can hear and catch DOM events (not custom Angular events) fired inside child components as the events bubble up.
  • Event bindings will be active once they are set on an element and they will stay active until the elements are removed from the DOM so that means you cannot control when to add or remove event listeners dynamically.

Stay tuned for the next blog posts on event listeners as I explore the other methods of listening to DOM events in Angular.