動態建立 @ViewChild 導致執行時錯誤的原因分析
本文討論問題的程式碼,位於 Github:https://github.com/wangzixi-diablo/ngDynamic
問題描述
我的 Component 程式碼如下圖所示:
-
使用依賴注入,引入
ViewContainerRef
,從而可以使用其createEmbeddedView
方法,在執行時動態建立例項。 -
使用
@ViewChild
,獲得該 Component HTML 原始碼裡定義的 id 為 tpl 的模板例項,型別為TemplateRef
。 -
Component 的 HTML 原始碼:
<ng-template tpl> <div> Hello, ng-template! </div> </ng-template>
然而啟動應用,出現執行時錯誤:
ERROR TypeError: Cannot read properties of undefined (reading 'createEmbeddedView')
at ViewContainerRef.createEmbeddedView (core.js:10190:45)
at NgTemplateComponent.push.8YnP.NgTemplateComponent.ngAfterViewInit (ng-template.component.ts:20:20)
at callHook (core.js:3281:18)
at callHooks (core.js:3251:17)
at executeInitAndCheckHooks (core.js:3203:9)
at refreshView (core.js:7451:21)
at renderComponentOrTemplate (core.js:7494:9)
at tickRootContext (core.js:8701:9)
at detectChangesInRootView (core.js:8726:5)
at RootViewRef.detectChanges (core.js:9991:9)
問題分析
上述呼叫上下文裡,有一個棧幀是我們應用程式的程式碼:
ngAfterViewInit (ng-template.component.ts:20:20)
在其方法內設定斷點, 發現執行時,this.tplRef 為空。
在值為 undefined 的變數上呼叫 createEmbeddedView
導致的這個錯誤。
問題轉化為:this.tplRef
的賦值邏輯是怎樣的?
在 ngOnInit 時,這個屬性還是 undefined 狀態:
我們把滑鼠 hover 在 @ViewChild
上檢視其說明:
變更檢測器會在檢視的 DOM 中查詢能匹配上該選擇器的第一個元素或指令。 如果檢視的 DOM 發生了變化,出現了匹配該選擇器的新的子節點,該屬性就會被更新。
發現輸入引數是一個選擇器,本例我傳入的選擇器是 id 選擇器:tpl
根據 Angular 官網文件,這意味著我的 HTML 模板檔案裡,tpl 之前應該用 #
修飾:
解決方案
在 tpl 前新增 #
:
總結
如果我們想進一步觀察 view query 是如何根據傳入的選擇器 tpl
,去 dom tree 裡查詢的節點,可以新增如下程式碼,即 @ViewChild 和 set 函式搭配使用的情況。
@ViewChild('tpl')
set thisNamedoesnotMatter(v:TemplateRef<any>){
console.log('Jerry');
this.tplRef = v;
}
通過除錯,發現 view query 的執行過程不會顯示在 Chrome 開發者工具裡,而僅僅顯示一個 dummy 的 XXX(Component 名稱)_Query 的呼叫上下文。
set 函式裡的輸入引數 v 代表的就是 id 為 tpl 的 Template 例項。