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