Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Josh Siok and Megan Siok
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Josh Siok@siok ) and Megan Siok

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

By Ben Nadel on

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:


 
 
 

 
 Multiple selector matches on a single element leads to only a single Directive instance. 
 
 
 

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.



Looking For A New Job?

Ooops, there are no jobs. Post one now for only $29 and own this real estate!

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.