[Angular] 結構性 Directive

Angular 的 directive 真的很厲害,幾乎什麼都能做,除了可以擴充原本 element 的功能外,也還可以做到結構上的控制變化,而這一類型的稱為 Structural directive

Structural directives—change the DOM layout by adding and removing DOM elements.

我們常用的 ngIf 就是其中之一,我們可以透過這一款的 directive 來新增或移除 DOM element。但在這之前,需要重新介紹 * 這一個語法糖

asterisk (*)

* 會用 <ng-template> 將 directive 所處的 element 包起來。[1]

1
2
3
4
5
6
7
<div *ngIf="hero" >{{hero.name}}</div>

// 上述將會轉換成下述

<ng-template [ngIf]="hero" >
<div>{{hero.name}}</div>
</ng-template>

ng-template 裡的內容可以透過 TemplateRef 取得

簡易版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Directive({ selector: '[myIf]'})
export class MyIfDirective {

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

@Input() set myIf(condition: boolean) {
if (condition) {
// 新增 DOM
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
// 移除 DOM
this.viewContainer.clear();
}
}
}

使用 directive 的方式

1
<div *myIf='condition'></div>
  • templteRef 的部分,請參閱上一區段的說明
  • ViewContainerRef 是指目前 directive的所在位置

透過這兩個物件,就可以完成 structural directive的實作,其實並不困難

稍微進階版

我們也可以將 service 注入到 directive 裡面,透過 RxJS 的幫助,可以讓 directive 處於自動監測的狀態,當全域某特定變數改變時,directive 也會跟著改變。聽起來很神奇,那實作起來會很困難嗎?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Directive({ selector: '[isAuth]'})
export class MyIfDirective {
user$ : Subscription;

constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
private userService: UserService) { }


ngOnInit() {
this.user$ = this.userService.user
.do(() => this.viewContainer.clear())
.filter(user => user.isLogin)
.subscribe(() => {
this.viewContainer.createEmbeddedView(this.templateRef);
});
}

ngOnDestroy() {
this.user$.unsubscribe();
}
}

打完收工,其實還蠻簡單的。

這寫法跟寫 Component 根本就沒有差別,這就是 Angular 的優點,程式碼格式的一致性很高。

參考閱讀


  1. Angular 4 以後,原本的<template> 會使用 <ng-template> 替代