HOWTO · Angular

Angular 中的 getElementById 替代方案

使用範本參照、ElementRef 和 Renderer2,在 Angular 中尋找元件元素並讀取輸入值。

本頁內容

要尋找屬於 Angular 元件的元素,可以為它加上一個範本參照,並使用 @ViewChild 查詢該參照。這樣查詢範圍被限定在元件內,而不是依 ID 搜尋整個文件。當元件程式碼需要原生元素參照時,使用 ElementRef;對於設定樣式等受支援的 DOM 操作,使用 Renderer2。這些 API 承擔著不同的職責;它們都不是 getElementById 的萬用一一替代品。

Angular 中 getElementById 的替代方案

瀏覽器的 document.getElementById("name") 會在文件中搜尋 id 為 name 的元素。在 Angular 元件中,當目標是該元件自身範本的一部分時,範本參照通常更合適。例如,在元件範本的輸入框上加上 #nameInput,然後在元件類別中使用 @ViewChild 查詢 nameInput。該參照將兩者連接起來,無需進行整個文件範圍的查找,也無需全域唯一的 ID。

當元件必須在某個事件之後讀取或更新特定控制項時,這種模式很有用。如果目標是在檢視和元件之間傳遞值,Angular 範本繫結可能比直接存取 DOM 更合適。只有當工作確實需要元素本身時,才使用原生元素參照,並將查找保持在擁有該範本的元件內部。

Angular 中的 ElementRef

ElementRef 是對原生元素參照的包裝。它的 nativeElement 屬性公開了底層元素;它不會為該元素加入 Angular 特有的方法。在元件範本中,用 #nameInput 標記輸入框;在元件類別中,用 @ViewChild("nameInput") nameInput!: ElementRef<HTMLInputElement>; 查詢那個確切的名稱。範本參照和查詢名稱必須一致,泛型類型則告訴 TypeScript 該原生元素是一個輸入框。

作為一個具體的搭配,範本可以包含 <input #nameInput type="text" value="Ada">。相應的類別宣告 @ViewChild("nameInput") nameInput!: ElementRef<HTMLInputElement>;,並可以在處理函式中用 getValue(): string { return this.nameInput.nativeElement.value; } 讀取該值。Angular 會為元件解析該查詢;應在檢視轉譯之後從事件中呼叫處理函式,而不是假設參照在建構期間就已就緒。

透過 nativeElement 的直接存取會使程式碼與平台的原生 DOM 綁定。這對於讀取輸入屬性這類範圍較窄的工作是合適的,但要避免將其用作 Angular 繫結的通用替代品。這也意味著程式碼不應假定在伺服器端轉譯等環境中存在瀏覽器 DOM。這裡不對任何 Angular 版本範圍作出斷言;請根據您的應用程式所使用的版本確認查詢語法和生命週期細節。

在 Angular 中使用 ElementRef 取得輸入的值

要讀取並重設輸入,請將範本和類別的部分放在一起。範本可以是 <input #nameInput type="text" value="Ada"><button (click)="showValue()">Read</button><button (click)="resetValue()">Reset</button>。在元件類別中,使用 @ViewChild("nameInput") nameInput!: ElementRef<HTMLInputElement>;,然後定義 showValue(): void { console.log(this.nameInput.nativeElement.value); } 和 resetValue(): void { this.nameInput.nativeElement.value = ""; }。#nameInput 標記是範本一側的參照;傳遞給 @ViewChild 的字串會選中它,而按鈕繫結會呼叫這些處理函式。

這是對原生元素的直接存取:值從輸入元素讀取或寫入輸入元素。如果該欄位是表單或應用程式狀態的一部分,當表單控制項或繫結能更好地表示資料流時,應優先使用它們。這些方法將值保留在 Angular 的模型中,可以使驗證和更新更易於協調。當工作明確要求與輸入元素互動時使用 ElementRef,並保持操作小而清晰。

下面的純 JavaScript 範例只模擬了這些處理函式所使用的原生輸入屬性存取。它不是一個 Angular 元件;上面的範本參照與 @ViewChild 的搭配展示了框架特有的連接方式。

const input = { value: "Ada" };
const component = {
  nameInput: { nativeElement: input },
  getValue() {
    return this.nameInput.nativeElement.value;
  },
  resetValue() {
    this.nameInput.nativeElement.value = "";
  }
};

console.log(component.getValue());
component.resetValue();
console.log(JSON.stringify(input.value));
Ada
""

上述 Angular 片段是說明性的範本和 TypeScript 片段,並非可獨立執行的範例。請將標記放在元件範本中,將查詢和處理函式放在該元件類別中;執行證據僅適用於純 JavaScript 模型,因此不對 Angular 輸出作出任何斷言。

Angular Renderer2

Renderer2 是 Angular 為已轉譯元素上的受支援操作提供的抽象。它與 ElementRef 不同:後者提供對原生元素參照的存取,而 Renderer2 提供執行諸如加入類別或設定樣式等操作的方法。當某個操作需要該抽象時,將其注入到元件中。它不會把 getElementById 變成 Angular 查詢,也不會使任意的 DOM 操作自動變得安全。

要為元件擁有的元素設定樣式,請在範本的 div 上加上 #styledDiv,並將其連接到諸如 @ViewChild("styledDiv") styledDiv!: ElementRef<HTMLElement>; 的查詢。在元件建構函式中注入 Renderer2。在 applyStyles() 處理函式中,呼叫 this.renderer.setStyle(this.styledDiv.nativeElement, "background-color", "lightblue"),然後再用 "border" 和 "1px solid darkblue" 呼叫一次。範本按鈕可以用 (click)="applyStyles()" 呼叫該處理函式。

將範本標記保留在 HTML 範本中,將查詢、注入和處理函式保留在 TypeScript 類別中;將兩個檔案合併到一個程式碼區塊中可能會掩蓋每個部分所屬的位置。這些呼叫說明了元件上下文中的 Angular API,並不是可獨立執行的範例。對於普通的呈現變更,類別繫結或樣式繫結可能更簡單,並使條件在範本中保持可見。當 Renderer2 的轉譯抽象適合該操作時使用它,並避免聲稱它能為每種用途保證安全性或變更偵測行為。

結論:Angular 中的 DOM 操作

對於元件擁有的元素,應從範本參照和 @ViewChild 開始,而不是在整個文件中用 getElementById 搜尋。僅當程式碼需要原生元素時,例如讀取或重設輸入的值時,才使用 ElementRef 及其 nativeElement。對於設定樣式等受支援的操作,使用 Renderer2。它們以不同的方式補充 Angular 的元件模型;請根據工作進行選擇,當繫結或表單控制項能更清晰地表達所需的檢視和資料流時,應優先使用它們。