Last update on Sunday, April 2nd 2017

Global Events and Event Delegation in Angular 2

In Angular 2, we are used to listening for events on the Component's DOM elements, however how do we listen to events outside of this range? Two ways to do this:

  1. Creating a Host Listener
  2. Using the Component's host property

The Host Listener Way

Angular 2 provides a HostListener Decorator in order to match an event triggered on a target with a function declared on the next line.
Just like this:

import { HostListener, Component } from "@angular/core";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  @HostListener("document:click", ["$event"])
  onDocumentClicked(ev) {
    console.log("clicked", ev);
  }
}

The syntax is as follow: 'target:event', [args].
When clicking on the document, we will be able to use the event triggered, you can also pass other arguments like $event.target for example.
However the target must be global, if you prefer, you can replace document by window.

The Host Way

Angular 2 Components provide a host property in order to match a function with an event triggered on a target.

import { HostListener, Component } from "@angular/core";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
  host: {
    "(document:click)": "onDocumentClicked($event)"
  }
})
export class AppComponent {
  onDocumentClicked(ev) {
    console.log("clicked", ev);
  }
}

The syntax is as follow: ('target:event)': 'function(args)'.
Same mechanism as the HostListener, only global and configuration friendly.

Event Delegation

Now that we have more experience under our belt, we can tackle one very important JavaScript concept: Event Delegation.

I discovered this concept thanks to the Great David Walsh, head there for a quick 5-minutes read.

5 minutes later

So how do we do this in Angular 2?
Let's start by using a similar template:

<ul #ulEl id="parent-list">
  <li id="post-1">Item 1</li>
  <li id="post-2">Item 2</li>
  <li id="post-3">Item 3</li>
  <li id="post-4">Item 4</li>
  <li id="post-5">Item 5</li>
  <li id="post-6">Item 6</li>
</ul>

Notice the #ulEl?
It's a reference that we will use in order to attach the listener in our Component here:

import {ElementRef, Renderer, ViewChild, Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor (private renderer: Renderer){ }
  @ViewChild('ulEl') ulEl: ElementRef;

  ngOnInit() {
    this.tmpListener = this.renderer.listen(this.ulEl.nativeElement, 'click', this.logElement);
  }

  // Shorthand to get event.target
  logElement({target}) {
      if(target && target.nodeName == "LI") {
      console.log('Target id: ', target.id);
      // Add Business Logic here
    }
  }

  ngOnDestroy() {
    this.tmpListener();
  }
}

We get our ul Element and stock it as an ElementRef.
When we init our Component, we use the Renderer (for cross-platform compatibility) in order to attach an EventListener.
If the <ul> or a <li> tag is clicked, we trigger the logElement function.

This function will acquire the target from the event, if this target is a <li> tag we log the id property.

Conclusion

As usual many ways to do the work.
I prefer to go with the host property of a Component, this solution clearly lists every listener - target - function matching.
Event Delegation is an interesting trick that can help you for specific cases, it's being discussed a lot in the community, some times as a savior (see the comments here) and on other cases just as a little plus (there).

Introduction to Ionic Google Analytics

Action Range and spooky Sounds in an AR Ionic app with Wikitude

Adding Sounds using HTML5 and Native Audio in Ionic

Stay up to date


Join over 4000 other developers who already receive my tutorials and their source code out of the oven + other free stuff!