Renderer2

Renderer2 的作用

Renderer2 是 Angular 提供的抽象層,用於安全地操作 DOM。它的主要目的是:

  1. 平台無關性:讓程式碼能在不同平台執行(瀏覽器、伺服器端、Web Worker)

  2. 安全性:防止 XSS 攻擊,自動清理危險內容

  3. 一致性:提供統一的 API 來操作 DOM

  4. 測試友善:更容易進行單元測試

為什麼使用 Renderer2 而不是直接操作 DOM?

// ❌ 不好的做法:直接操作 DOM
document.getElementById('myElement').innerHTML = userInput;

// ✅ 好的做法:使用 Renderer2
this.renderer.setProperty(element, 'innerHTML', userInput); // 會自動清理

常用方法分類

1. 元素創建與移除

// 創建元素
createElement(name: string, namespace?: string): any
const div = this.renderer.createElement('div');
const svg = this.renderer.createElement('circle', 'http://www.w3.org/2000/svg');

// 創建文字節點
createText(value: string): any
const textNode = this.renderer.createText('Hello World');

// 創建註解
createComment(value: string): any
const comment = this.renderer.createComment('This is a comment');

// 移除子元素
removeChild(parent: any, oldChild: any, isHostElement?: boolean): void
this.renderer.removeChild(parentElement, childElement);

// 銷毀節點
destroyNode(node: any): void
this.renderer.destroyNode(element);

2. DOM 結構操作

// 添加子元素
appendChild(parent: any, newChild: any): void
this.renderer.appendChild(this.el.nativeElement, newDiv);

// 插入元素
insertBefore(parent: any, newChild: any, refChild: any, isMove?: boolean): void
this.renderer.insertBefore(parent, newElement, referenceElement);

// 選擇器查詢(盡量少用)
selectRootElement(selectorOrNode: string | any, preserveContent?: boolean): any

3. 屬性操作

// 設定屬性
setAttribute(el: any, name: string, value: string, namespace?: string): void
this.renderer.setAttribute(element, 'id', 'myId');
this.renderer.setAttribute(element, 'aria-label', 'Close button');

// 移除屬性
removeAttribute(el: any, name: string, namespace?: string): void
this.renderer.removeAttribute(element, 'disabled');

// 設定特性(property)
setProperty(el: any, name: string, value: any): void
this.renderer.setProperty(element, 'innerHTML', '<span>Safe content</span>');
this.renderer.setProperty(element, 'value', inputValue);

4. 樣式操作

// 設定樣式
setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2): void
this.renderer.setStyle(element, 'color', 'red');
this.renderer.setStyle(element, 'width', '100px');
this.renderer.setStyle(element, 'display', 'none');

// 移除樣式
removeStyle(el: any, style: string, flags?: RendererStyleFlags2): void
this.renderer.removeStyle(element, 'color');

// 添加/移除 CSS 類別
addClass(el: any, name: string): void
removeClass(el: any, name: string): void
this.renderer.addClass(element, 'active');
this.renderer.removeClass(element, 'hidden');

5. 事件處理

// 監聽事件
listen(target: 'window' | 'document' | 'body' | any, eventName: string, callback: (event: any) => boolean | void): () => void

// 監聽元素事件
const unlisten = this.renderer.listen(element, 'click', (event) => {
  console.log('Clicked!', event);
});

// 監聽全域事件
const unlistenWindow = this.renderer.listen('window', 'resize', (event) => {
  console.log('Window resized');
});

// 移除事件監聽器
unlisten(); // 呼叫返回的函數來移除監聽

6. 其他實用方法

// 設定值(通常用於表單元素)
setValue(node: any, value: string): void
this.renderer.setValue(inputElement, 'new value');

// 獲取父節點
parentNode(node: any): any
const parent = this.renderer.parentNode(element);

// 獲取下一個兄弟節點
nextSibling(node: any): any
const sibling = this.renderer.nextSibling(element);

在你的 Directive 中的具體應用

export class  {
  private renderer = inject(Renderer2);
  private el = inject(ElementRef);
  
  private createIframe(): void {
    // 創建元素
    const iframe = this.renderer.createElement('iframe');
    
    // 設定屬性
    this.renderer.setAttribute(iframe, 'src', this.iframeSrc());
    this.renderer.setAttribute(iframe, 'frameborder', '0');
    
    // 設定樣式
    this.renderer.setStyle(iframe, 'width', '100%');
    this.renderer.setStyle(iframe, 'height', '500px');
    this.renderer.setStyle(iframe, 'border', 'none');
    
    // 監聽事件
    const unlisten = this.renderer.listen(iframe, 'load', () => {
      console.log('Iframe loaded');
      unlisten(); // 移除監聽器
    });
    
    // 添加到 DOM
    this.renderer.appendChild(this.el.nativeElement, iframe);
  }
}

最佳實踐

1. 記得清理資源

ngOnDestroy(): void {
  // 移除事件監聽器
  if (this.unlistenFn) {
    this.unlistenFn();
  }
  
  // 清理 DOM 節點
  if (this.createdElement) {
    this.renderer.removeChild(this.el.nativeElement, this.createdElement);
  }
}

2. 避免直接 DOM 操作

// ❌ 避免這樣做
this.el.nativeElement.innerHTML = '<div>content</div>';
this.el.nativeElement.style.color = 'red';

// ✅ 使用 Renderer2
this.renderer.setProperty(this.el.nativeElement, 'innerHTML', '<div>content</div>');
this.renderer.setStyle(this.el.nativeElement, 'color', 'red');

3. 處理平台差異

import { PLATFORM_ID, inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

constructor() {
  const platformId = inject(PLATFORM_ID);
  
  if (isPlatformBrowser(platformId)) {
    // 只在瀏覽器環境執行
    this.setupBrowserSpecificFeatures();
  }
}

效能考量

  1. 批量操作:盡量一次設定多個屬性,避免頻繁的 DOM 操作

  2. 事件移除:記得移除事件監聽器避免記憶體洩漏

  3. 元素回收:移除不需要的 DOM 元素

Renderer2 是 Angular 中 DOM 操作的標準方式,使用它能確保你的程式碼更安全、更可測試,且能在不同平台正常運作。

Last updated