關於 Angular view Query 的 id 選擇器問題的單步除錯
問題描述
我有這樣一個 Angular Component,模板檔案如下:
@Component({
selector: 'example-app',
template: `
<pane id="1" *ngIf="shouldShow">
<pane id="2" *ngIf="!shouldShow">
<button (click)="toggle()">Toggle</button> <div id="panel 1?" #pane999>1</div> <div id="panel 2?" pane>2</div> <div>Selected: {{selectedPane}}</div>
`,
})
在其 Component 實現裡,我期望通過 `@ViewChild`,在執行時拿到 id 為 `panel 1?` 的 div 元素的例項。 ```typescript export class ViewChildComp { constructor(public changeDetectorRef: ChangeDetectorRef ){} @ViewChild("panel 1?") set panethis(v) { //setTimeout(()=> this.selectedPane = v.id, 0); this.selectedPane = v.id; this.changeDetectorRef.detectChanges(); //Promise.resolve().then(() => this.selectedPane = v.id); }
然而執行時沒有得到期望的結果,報錯:
ERROR TypeError: Cannot read properties of undefined (reading 'id')
at ViewChildComp.set (ng-content.component.ts:57:27)
at ViewChildComp_Query (template.html:3:5)
at executeViewQueryFn (core.js:8758:5)
at refreshView (core.js:7437:13)
at refreshComponent (core.js:8527:13)
at refreshChildComponents (core.js:7186:9)
at refreshView (core.js:7430:13)
at refreshComponent (core.js:8527:13)
at refreshChildComponents (core.js:7186:9)
at refreshView (core.js:7430:13)
問題分析
我們點選上圖高亮的 template.html
呼叫棧:
來到我們自己 Component 的模板檔案,因為這是一個內聯到 Component 裡的模板,所以顯示為 template.html
:
通過單步除錯,能發現在 refreshView
裡,執行 view query
的入口邏輯:
function executeViewQueryFn(flags, viewQueryFn, component) {
ngDevMode && assertDefined(viewQueryFn, 'View queries function to execute must be defined.');
setCurrentQueryIndex(0);
viewQueryFn(flags, component);
}
根據關鍵字 view query
查詢 Angular 官網,發現在 view query 裡使用 id 選擇器的正確語法,並不是直接查詢 HTML 元素的 id 屬性,而是需要在 HTML 元素或者 ng-template
裡使用符號 #
指定一個 id,然後將這個 id 傳入 @ViewChild
:
修改之後的執行效果:
總結
view query 在父 Component 需要訪問其子 Component 的場景下特別有用。
假設有一個 alert Component:
@Component({
selector: 'alert',
template: `
<h1 (click)="alert()">{{type}}</h1>
`,
})
export class AlertComponent {
@Input() type: string = "success";
alert() {
console.log("alert");
}
}
在我們的父 Component 裡,可以定義 AlertComponent 的多個例項:
@Component({
selector: 'my-app',
template: `
<alert></alert>
<alert type="danger"></alert>
<alert type="info"></alert>
`,
})
export class App {
@ViewChildren(AlertComponent) alerts: QueryList<AlertComponent>
ngAfterViewInit() {
this.alerts.forEach(alertInstance => console.log(alertInstance));
}
}
我們可以使用 @ViewChildren 裝飾器從宿主檢視中抓取元素。
@ViewChildren 裝飾器支援指令或元件型別作為引數,或模板變數的名稱。
當引數是元件/指令時,返回值將是元件/指令例項。
上面例子的 console.log 語句會列印 AlertComponent 的例項: