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:

Creating a Host Listener 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.

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 ) ; } logElement ( { target } ) { if ( target && target . nodeName == "LI" ) { console . log ( 'Target id: ' , target . id ) ; } } 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).