關於 ng-template 通過 @input 傳入另一個 Component 不能工作的問題除錯
問題描述
本文設計到的程式碼倉庫:https://github.com/wangzixi-diablo/ngDynamic
host Component:
<ng-template #paramTemplate>
<div>我是#paramTemplate裡的 div 標籤</div>
</ng-template>
<app-template-input inputTemplate='paramTemplate'></app-template-input>
我期望把 id 為 paramTemplate
的模板例項傳入子 Component:app-template-input
然而執行時 Chrome console 報錯:
這些錯誤沒有一個是在我書寫的程式碼之內:
ERROR TypeError: templateRef.createEmbeddedView is not a function
at ViewContainerRef.createEmbeddedView (core.js:10190:45)
at NgTemplateOutlet.ngOnChanges (ng_template_outlet.ts:65:28)
at NgTemplateOutlet.rememberChangeHistoryAndInvokeOnChangesHook (core.js:2373:14)
at callHook (core.js:3285:14)
at callHooks (core.js:3251:17)
at executeInitAndCheckHooks (core.js:3203:9)
at refreshView (core.js:7395:21)
at refreshComponent (core.js:8527:13)
at refreshChildComponents (core.js:7186:9)
at refreshView (core.js:7430:13)
問題分析
從呼叫棧 ViewContainerRef.createEmbeddedView
開始分析。設定斷點開始除錯:
重新重新整理頁面,斷點觸發:
檢查變數 templateRef
,發現它的值是一個字串。
但是接下來的語句,需要呼叫 templateRef
的 createEmbeddedView
方法。顯然,string 型別的變數,其原型鏈上是不可能有這個方法的,所以報錯。
我們注意到,當前的呼叫棧,發生在 ngTemplateOutlet
的 ngOnChanges
方法內。
檢視 app-template-input
的實現,發現 @Input 接收的資料型別為 TemplateRef
createEmbeddedView
方法。
我們的實現裡,錯誤地給其傳入了一個字串。
解決辦法
給屬性 inputTemplate
加上一對中括號,使用表示式繫結語法,這樣 paramTemplate
會被當作一個表示式執行,導致第 48 行的 paramTemplate 例項被傳給 @Input:
最後問題解決:
總結
這裡我們用 ng-container
裡包裹了一個結構指令 *ngTemplateOutlet
.
看 app-template-input 的實現。
任何消費 TemplateInputComponent 的 parent Component,都需要給第 18 行的 @Input 賦一個模板例項。
賦值語法如下:
<app-template-input [inputTemplate]="paramTemplate"></app-template-input>
看起來給 inputTemplate 賦值的內容 paramTemplate
是一個字串,實際是一個表示式
。
最後生成的原始碼:
這裡使用 *ngTemplateOutlet
,動態生成一個 template 例項。
也就是說,ng-template 用 #
定義的 id,和 ng-container
, *ngTemplateOutlet
這三者是配合起來使用的。