自定義結構型 Directive

這篇筆記介紹如何自定義一個結構型 Directive,其功能類似於 Angular 內建的 @if(或舊版的 *ngIf)。

基本用法

建立一個名為 appAuth 的結構型 Directive,用來根據用戶權限顯示或隱藏元素:

<p appAuth="admin">Only admins should see this!</p>

Directive 的基本實現

首先,我們在 Directive class 中定義必要的輸入和依賴:

// auth.directive.ts
import { Directive, input, effect, inject, TemplateRef, ViewContainerRef } from '@angular/core';
import { AuthService } from './auth.service';
import { Permission } from './permission.model';

@Directive({
  selector: '[appAuth]',
  standalone: true
})
export class AuthDirective {
  // 使用 input() 設定輸入屬性,並給予別名
  userType = input.required<Permission>({ alias: 'appAuth' });
  
  // 注入必要的服務和參考
  private authService = inject(AuthService);
  
  constructor() {
    effect(() => {
      if (this.authService.activePermission() === this.userType()) {
        console.log('SHOW');
      } else {
        console.log('DONT SHOW');
      }
    });
  }
}

在這段程式碼中,我們:

  • 使用 input() 定義了一個輸入屬性,並透過 alias 設定別名為 appAuth(與選擇器相同)

  • 注入了 AuthService 以獲取當前用戶權限

  • 使用 effect() 在相關依賴變化時執行權限檢查

但這樣只能在控制台輸出訊息,還無法真正控制 DOM 的顯示與隱藏。

使用 ng-template 和結構型 Directive

要實現真正的結構型 Directive,我們需要:

  1. 使用 ng-template 包裝目標內容:

<ng-template appAuth="admin">
  <p>Only admins should see this!</p>
</ng-template>
  1. 在 Directive 中注入 TemplateRefViewContainerRef

export class AuthDirective {
  userType = input.required<Permission>({ alias: 'appAuth' });
  private authService = inject(AuthService);
  
  // 注入 TemplateRef 和 ViewContainerRef
  private templateRef = inject(TemplateRef);
  private viewContainerRef = inject(ViewContainerRef);
  
  constructor() {
    effect(() => {
      if (this.authService.activePermission() === this.userType()) {
        // 顯示內容
        this.viewContainerRef.createEmbeddedView(this.templateRef);
      } else {
        // 清除內容
        this.viewContainerRef.clear();
      }
    });
  }
}

這裡的關鍵點是:

  • TemplateRef:引用包含在 ng-template 中的內容

  • ViewContainerRef:指向 DOM 中應該插入內容的位置

  • createEmbeddedView():將模板內容渲染到視圖容器中

  • clear():從視圖容器中移除所有內容

當我們進入頁面的時候不會看到那句話出現在畫面上,但在 element 裡面可以看到一個 Angular 的標記,可以找到當符合條件時內容應該呈現的地方。如圖:

當條件符合時,模板內容會被渲染;不符合時,內容會被移除。

使用星號語法糖(*)

Angular 提供了星號(*)作為語法糖,讓我們可以更簡潔地使用結構型 Directive:

<p *appAuth="'admin'">Only admins should see this!</p>

這等同於之前的 ng-template 寫法,但更簡潔。注意這裡的 'admin' 需要用單引號包起來,因為它在引號內被當作 TypeScript 表達式處理。

使用星號語法時,Angular 會在背後:

  1. 創建一個 ng-template 元素

  2. 將原始元素移入模板中

  3. 在模板上應用 Directive

結論

自定義結構型 Directive 需要:

  1. 定義輸入屬性接收條件

  2. 注入 TemplateRefViewContainerRef

  3. 根據條件使用 createEmbeddedView()clear() 控制內容的顯示


Last updated