1. 程式人生 > 其它 >動態建立 @ViewChild 導致執行時錯誤的原因分析

動態建立 @ViewChild 導致執行時錯誤的原因分析

本文討論問題的程式碼,位於 Github:https://github.com/wangzixi-diablo/ngDynamic

問題描述

我的 Component 程式碼如下圖所示:

  1. 使用依賴注入,引入 ViewContainerRef,從而可以使用其 createEmbeddedView 方法,在執行時動態建立例項。

  2. 使用 @ViewChild,獲得該 Component HTML 原始碼裡定義的 id 為 tpl 的模板例項,型別為 TemplateRef

  3. 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 例項。