@ViewChild 讓你在元件的 TypeScript 程式碼中直接取得模板裡的 DOM 元素或子元件的參考。
當需要直接操作 DOM 或呼叫子元件的方法時,@ViewChild 是最直接的解決方式。
取得 DOM 元素
在模板中用 # 宣告一個範本變數,再用 @ViewChild 在 TypeScript 中取得它的參考。
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
@Component({
standalone: true,
selector: 'app-demo',
template: `
`,
})
export class DemoComponent implements AfterViewInit {
@ViewChild('nameInput') nameInput!: ElementRef;
ngAfterViewInit() {
console.log(this.nameInput.nativeElement);
}
focusInput() {
this.nameInput.nativeElement.focus();
}
} @ViewChild('nameInput') 接收範本變數名稱作為參數,回傳對應元素的 ElementRef。
透過 ElementRef.nativeElement 可以存取原生 DOM 元素。
取得子元件
@ViewChild 也可以取得子元件的參考,讓你直接呼叫子元件的方法或存取它的屬性。
子元件
import { Component } from '@angular/core';
@Component({
standalone: true,
selector: 'app-timer',
template: `{{ seconds }} 秒
`,
})
export class TimerComponent {
seconds = 0;
start() {
setInterval(() => {
this.seconds++;
}, 1000);
}
reset() {
this.seconds = 0;
}
}父元件
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { TimerComponent } from './timer.component';
@Component({
standalone: true,
imports: [TimerComponent],
selector: 'app-parent',
template: `
`,
})
export class ParentComponent implements AfterViewInit {
@ViewChild(TimerComponent) timer!: TimerComponent;
ngAfterViewInit() {
console.log(this.timer); // TimerComponent 的實例
}
startTimer() {
this.timer.start();
}
resetTimer() {
this.timer.reset();
}
}用類別名稱 (TimerComponent) 而非字串作為參數,取得的就是子元件的實例,可以直接呼叫它的方法。
@ViewChildren
如果模板中有多個相同的元素或元件,用 @ViewChildren 可以取得所有的參考,回傳一個 QueryList:
import { Component, ViewChildren, QueryList, ElementRef, AfterViewInit } from '@angular/core';
@Component({
standalone: true,
selector: 'app-demo',
template: `
`,
})
export class DemoComponent implements AfterViewInit {
@ViewChildren('item') items!: QueryList>;
ngAfterViewInit() {
console.log(this.items.length); // 3
}
focusAll() {
this.items.forEach(item => item.nativeElement.focus());
}
} QueryList 支援 forEach、map、filter 等方法,也可以用 changes Observable 監聽清單的變化:
ngAfterViewInit() {
this.items.changes.subscribe(() => {
console.log('清單有變化');
});
}注意事項
只能在 ngAfterViewInit 之後使用
@ViewChild 的值在 ngAfterViewInit 生命週期之後才會被填入,在此之前值是 undefined:
ngOnInit() {
console.log(this.nameInput); // undefined,視圖尚未初始化
}
ngAfterViewInit() {
console.log(this.nameInput); // ElementRef,可以正常使用
}static 選項
@ViewChild 有一個 static 選項,預設是 false:
static: false(預設):在變更偵測後解析,可以在ngAfterViewInit中使用static: true:在變更偵測前解析,可以在ngOnInit中使用,但只適用於靜態的元素 (不在*ngIf或*ngFor內的元素)
@ViewChild('nameInput', { static: true }) nameInput!: ElementRef;
ngOnInit() {
console.log(this.nameInput); // 可以使用 (因為 static: true)
}避免過度使用
直接操作 DOM 繞過了 Angular 的變更偵測機制,可能導致難以追蹤的問題。
優先考慮用資料綁定解決問題,只在真正需要直接操作 DOM 時才使用 @ViewChild。
viewChild() (Angular 17+)
Angular 17 引入了 Signal-based 的 viewChild() 函式,作為 @ViewChild 的現代替代:
import { Component, viewChild, ElementRef, AfterViewInit } from '@angular/core';
@Component({
standalone: true,
selector: 'app-demo',
template: ``,
})
export class DemoComponent {
nameInput = viewChild('nameInput');
// 或是必填版本:
nameInputRequired = viewChild.required('nameInput');
} viewChild() 回傳的是 Signal,讀取時需要加 ():
ngAfterViewInit() {
console.log(this.nameInput()?.nativeElement);
console.log(this.nameInputRequired().nativeElement); // 必填,不需要 ?
}@ViewChild 與 viewChild() 的差異
@ViewChild | viewChild() | |
|---|---|---|
| 語法 | 裝飾器 | 函式 |
| 回傳型別 | 直接值 | Signal |
| 讀取值 | 直接讀取屬性 | 需要加 () |
| 必填 | 用 ! 斷言 | viewChild.required() |
| Angular 版本 | 所有版本 | v17+ |
總結
@ViewChild:取得單一 DOM 元素或子元件的參考@ViewChildren:取得多個相同元素或元件的QueryList- 值在
ngAfterViewInit之後才可用 - Angular 17+ 提供 Signal-based 的
viewChild(),是更現代的寫法