Angular Directives

This article on Angular Directives is part of the Learning Angular series.

Understanding Angular Directives

Directives are instructions in the DOM
Receives a Green Background

@Directive({
    selector: '[appTurnGreen]'
})
export class TurnGreenDirective {
    …
}

Using ngIf to output data conditionally

Usage:
Created Server {{ serverName }}
TODO
Anything can be passed to the *ngIf directive that can return a Boolean. Based on true/false, the element is either added/removed from the DOM.

Enhanced ngIf with the Else condition

<p *ngIf="serverCreated else noServer">Created Server {{ serverName }}</p>
<ng-template #noServer>
    <p>No Server Created</p>
</ng-template>

Using the #noServer marks a template for using somewhere else.

Attribute Directives vs Structural Directives

Attribute Directive – Looks like a normal HTML attribute, which only affects the element they are added to.
Structural Directive – Looks like a normal HTML attribute with a leading * (for desugaring), and affects the whole DOM, as the elements get added/removed based on it. For example: *ngIf

ngFor and ngIf recap

We can’t have more than one structural directive on one element

Creating a Basic Attribute Directive

basic-highlight.directive.ts

import { Directive, ElementRef, OnInit } from '@angular/core';

@Directive({
    selector: '[appBasicDirective]'
})
export class BasicHighlightDirective {
    constructor(private elementRef:ElementRef) { }
    ngOnInit() {
        this.elementRef.nativeElement.style.backgroundColor = 'green';
    }
}

To make this directive available for use in the application, this has to added to AppModule in the declarations tag

@NgModule({
    declarations: [
       AppComponent,
       BasicHighlightDirective
    ]
    …
})

And to use it, simply do:

<p appBasicDirective>This is a highlighted element.</p>

Using the Renderer to build a Better Attribute Directive

The previous method is not a good practice, as it directly access and sets the values in the DOM
better-highlight.directive.ts

import { Directive, ElementRef, OnInit, Renderer2 } from '@angular/core';
@Directive({
selector: '[appBetterDirective]'
})
export class BetterHighlightDirective {
constructor(private elementRef:ElementRef, private renderer:Renderer2) {
}
ngOnInit() {
this.renderer.setStyle(this.elementRef.nativeElement, 'background-color', 'blue');
// The fourth argument is optional.
}
}

To make this directive available for use in the application, this has to added to AppModule in the declarations tag

@NgModule({
    declarations: [
       AppComponent,
       BetterHighlightDirective
    ]
    …
})

And to use it, simply do:

<p appBetterDirective>This is a highlighted element.</p>

Its better to use the BetterDirective as because Angular runs on multiple platforms, like the service workers, where there is no access to the DOM, so the BasicDirective method won’t work.

Using HostListener to Listen to Host Events

The class BetterHighlightDirective can be upgraded to listen to Host Events by using:

@HostListener('event-name') localEventName(event:Event) {
    // Do the specified tasks.
}

For example:

@HostListener('mouseenter') onMouseEnter(event:Event) {
    this.renderer.setStyle(this.elementRef.nativeElement, 'background-color', 'blue', false, false);
    // Check why there are multiple false arguments.

}
@HostListener('mouseleave') onMouseLeave(event:Event) {
    this.renderer.setStyle(this.elementRef.nativeElement, 'background-color', 'transparent', false);
}

Using HostBinding to Bind to Host Properties

There is also another easy way to update the view without using the renderer.

@HostBinding('propertt-to-bind-to') localPropertyName:string = 'default-value';

For example:

@HostBinding('style.backgroundColor') myBackgroundColor:string = 'transparent';
…
@HostListener('mouseenter') onMouseEnter(event:Event) {
    this.myBackgroundColor = "blue";
}
@HostListener('mouseleave') onMouseLeave(event:Event) {
    this.myBackgroundColor = 'transaparent';
}

Binding to Directive Properties

In the above method, the data are hard-coded and could not be passed by other developers from other classes, or this directive might not be reusable in multiple places.

@Input() defaultColor:string = 'transparent';
@Input() hoverColor:string = 'green';

And can be used to set in the Directive class:

this.myBackgroundColor = hoverColor;

Now this can be accessed from outside:

<p appBetterHighlight [defaultColor]="'yellow'" [hoverColor]="'red'">better-directives works!</p>

Even if the there are multiple directives in the above line, it might be confusing as to how Angular knows that the directives are of the element or the AttributeDirective. The fact is that Angular automatically knows it by looking at the properties available.

There is also another method to use the Attribute Selector. If the AttributeSelector is also used as an alias in one of the Input’s, then the element where the attribute is used will need some change.

@Input('appBetterHighlight') defaultColor:string = 'transparent';

Then it needs to be used as: (Not a preferrable method)

<p [appBetterHighlight]="'red'" [defaultColor]="'yellow'">better-directives works!</p>

Also in the above cases, it might be noted that the attribute binding is being used by placing square-brackets around it. They can be removed as for using a shortcut, but then the single-quotation-marks around the values will also need to be removed.

<p [appBetterHighlight]="'red'" defaultColor="yellow">better-directives works!</p>

But before using this, one needs to be very sure that the keys don’t pertain to any of the actual attributes of the element. And it is a custom attribute for property binding.

What happens behind the scenes on Structural Directives

When using a * in Structural Directives, Angular converts the given piece of template for its own use.

<div *ngIf="isOdd">
    <p>This is odd</p>
</div>

Gets converted to:

<ng-template>
    <div [ngIf]="isOdd">
        <p>This is odd</p>
    </div>
</ng-template>

Building a Structural Directive

@Directive({
  selector: '[appUnless]'
})
export class UnlessDirective {
    @Input() set appUnless(value:boolean) {
        if (value) {
          this.vcRef.clear();
        }
        else {
          this.vcRef.createEmbeddedView(this.templateRef);
        }
    }
    constructor(private templateRef:TemplateRef, private vcRef:ViewContainerRef) { }
}

One needs to make sure tha the name of the Input matches the name of the Directive to be set, as internally Angular internally converts the StructuralDirective tag to a PropertyBinding tag.
Now this Structural Directive can be used as:

<p *appUnless="false">Show this conditionally</p>

Understanding ngSwitch

<div [ngSwitch]="myValue">
    <p *ngSwitchCase="5">value is 5</p>
    <p *ngSwitchCase="10">value is 10</p>
    <p *ngSwitchDefault>value is Default</p>
</div>
where myValue is a variable.

Leave a Reply