Angular 2 – Directives Deep dive

Attribute vs Structural Directives

  • In attribute directives you never modify the DOM structure , just change some behaviour in the element where it is added to. I looks like a normal HTML attribute (with databinding or property binding).
  • In Structural directives the DOM is modified. It also looks like a normal HTML attribute but it has a leading *. Affects the whole area in the DOM (elements get added / removed)

Creating a custom directive

app.component.html

<p appBasicHighlight>this is a basic highlight test</p>

basic-highlight.directive.js

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

@Directive({
  selector: '[appBasicHighlight]'
})
export class BasicHighlightDirective implements OnInit {

  constructor(private elementRef: ElementRef) { }

  ngOnInit() {
    this.elementRef.nativeElement.style.backgroundColor = 'green';
  }

}

Shortcut on ng cli to create a directive:

ng g d basic-highlight

Using the Renderer to build a better attribute directive

The problem with the former approach is that when we access the native element directly we are giving up on Angular’s DOM abstraction and miss out on the opportunity to be able to execute also in none-DOM environments such as: native mobile, native desktop, web worker or server side rendering.

Remember that Angular is a platform, and the browser is just one option for where we can render our app.

So what you do is to give this responsibility to the Renderer class.

better-highlight.directive.ts

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

@Directive({
  selector: '[appBetterHighlight]'
})
export class BetterHighlightDirective implements OnInit {

  constructor(private elRef: ElementRef, private renderer: Renderer2) { }

  ngOnInit() {
    this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue');
  }
}

Using HostListener to listen directive events

better-highlight.directive.ts

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

@Directive({
  selector: '[appBetterHighlight]'
})
export class BetterHighlightDirective implements OnInit {

  constructor(private elRef: ElementRef, private renderer: Renderer2) { }

  ngOnInit() {
    // this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue');
  }

  @HostListener('mouseenter') mouseenter() {
    this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue');
  }
  @HostListener('mouseleave') mouseleave() {
    this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'transparent');
  }
}

Using HostBinding to Bind to element properties

better-highlight.directive.ts

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

@Directive({
  selector: '[appBetterHighlight]'
})
export class BetterHighlightDirective implements OnInit {

  @HostBinding('style.backgroundColor') backgroundColor: string = 'transparent';

  constructor(private elRef: ElementRef, private renderer: Renderer2) { }

  ngOnInit() {
    // this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue');
  }

  @HostListener('mouseenter') mouseenter() {
    // this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue');
    this.backgroundColor = 'blue';
  }
  @HostListener('mouseleave') mouseleave() {
    // this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'transparent');
    this.backgroundColor = 'transparent';
  }
}

Binding to directive properties

better-highlight.directive.ts

import { Directive,
  Renderer2,
  OnInit,
  Input,
  ElementRef,
  HostListener,
  HostBinding } from '@angular/core';

@Directive({
  selector: '[appBetterHighlight]'
})
export class BetterHighlightDirective implements OnInit {

  @Input() defaultColor: string = 'transparent';
  @Input() highlightColor: string = 'blue';

  @HostBinding('style.backgroundColor') backgroundColor: string = 'transparent';

  constructor(private elRef: ElementRef, private renderer: Renderer2) { }

  ngOnInit() {
    this.renderer.setStyle(this.elRef.nativeElement, 'background-color', this.defaultColor);
  }

  @HostListener('mouseenter') mouseenter() {
    // this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue');
    this.backgroundColor = this.highlightColor;
  }
  @HostListener('mouseleave') mouseleave() {
    // this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'transparent');
    this.backgroundColor = this.defaultColor;
  }
}

app-component.html

      <p appBetterHighlight [defaultColor]="'yellow'" [highlightColor]="'red'" >this is a Better highlight test</p>

Structural directives: What happens behind the scenes

  • For example *ngIf directive it is translated in a ng-template with ngIf attribute behind the scenes:
<div *ngIf="condition"> ... </div>
<ng-template [ngIf]="condition"></ng-template>

Building a custom structural directive

unless.directive.ts

import { Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';

@Directive({
  selector: '[appUnless]'
})
export class UnlessDirective {

  @Input() set appUnless(condition: boolean) {
    if(condition) {
      this.vcRef.createEmbeddedView(this.templateRef);
    } else {
      this.vcRef.clear();
    }

  }
  constructor(private templateRef: TemplateRef<any>, private vcRef: ViewContainerRef) { }

}

app.component.html

      <p *appUnless="myCondition">Hola caracola (custom structural directive)</p>

Undestanding ngSwitch

      <div [ngSwitch]="value">
        <p *ngSwitchCase="5">Value is 5</p>
        <p *ngSwitchCase="10">Value is 10</p>
        <p *ngSwitchCase="15">Value is 15</p>
      </div>

gg

fg

fg

fg

fg

fg

gf

gf

gf

gf

g

gf