Matching Multiple Selectors On The Same Element Creates A Single Directive Instance In Angular 5.0.0

Yesterday, when I was experimenting with the IntersectionObserver API for use in the lazy-loading of images, I started to think about emitting several different events on a single element. This got me thinking about directive selectors. We've already looked at the fact that event names can be used as selectors for Angular Directives; but, I wasn't sure what would happen if I matched multiple selectors for the same directive on the same element. It turns out, just as you would hope, only a single directive instance gets created even if multiple selectors for that directive are matched on the same element.

Run this demo in my JavaScript Demos project on GitHub.

To test this, I created a single attribute Directive that allowed for two different attribute selectors:

[everySecond]

[everyTwoSeconds]

These selectors also happen to be the names of two different events that emit time-stamps from the attribute Directive:

// Import the core angular services. import { Directive } from "@angular/core"; import { ElementRef } from "@angular/core"; import { EventEmitter } from "@angular/core"; import { OnDestroy } from "@angular/core"; import { OnInit } from "@angular/core"; // ----------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------- // // Notice that we have TWO ATTRIBUTE SELECTORS defined in our directive selectors. This // way, you can use [everySecond], [everyTwoSeconds], or BOTH at the same time. @Directive({ selector: "[everySecond] , [everyTwoSeconds]", outputs: [ "everySecond", "everyTwoSeconds" ] }) export class PingDirective implements OnInit, OnDestroy { public everySecond: EventEmitter<number>; public everyTwoSeconds: EventEmitter<number>; private timers: number[]; // I initialize the ping directive. constructor( elementRef: ElementRef ) { this.everySecond = new EventEmitter(); this.everyTwoSeconds = new EventEmitter(); this.timers = []; // Log that this constructor was called so that we can see what happens when both // of our directive selectors are matched on the same element at the same time. console.group( "PingDirective instantiated." ); console.log( elementRef.nativeElement ); console.groupEnd(); } // --- // PUBLIC METHODS. // --- // I get called once when the directive is being destroyed. public ngOnDestroy() : void { this.timers.forEach( clearInterval ); } // I get called once after the inputs have been bound for the first time. public ngOnInit() : void { this.timers.push( setInterval( () : void => { this.everySecond.emit( Date.now() ); }, 1000 ), setInterval( () : void => { this.everyTwoSeconds.emit( Date.now() ); }, 2000 ) ); } }

There's not much happening here - I'm setting up two events and logging the constructor details. Then, in my app component, I'm matching both of the directive's selectors on the same P-tag:

// Import the core angular services. import { Component } from "@angular/core"; // ----------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------- // @Component({ selector: "my-app", styleUrls: [ "./app.component.less" ], template: ` <p (everySecond)="ping1 = $event" (everyTwoSeconds)="ping2 = $event"> Every Second: {{ ping1 }}<br /> Every Two Seconds: {{ ping2 }} </p> ` }) export class AppComponent { public ping1: number = 0; public ping2: number = 0; }

As you can see, my P-tag attributes match both attribute selectors of my attribute Directive:

<p (everySecond)="..." (everyTwoSeconds)="...">

And, when we run this Angular application in the browser, we get the following output:

As you can see, even through we matched both of the PingDirective's attribute selectors on the same P-tag, only a single instance of the PingDirective is created for that element. This is exactly the kind of behavior we would hope to see. This provides a great deal of flexibility in how a Directive can be consumed in Angular 5.0.0; and, it does so in a way that doesn't produce unexpected directive instantiation.







