1、引言
今天看到文章:https://segmentfault.com/a/1190000015944548 。因而專門研究一下ngTemplateOutlet用法!!!!javascript
官方定義 :
NgTemplateOutlet: 它是結構指令,根據一個提早備好的 TemplateRef
插入一個內嵌視圖。 你能夠經過設置 [ngTemplateOutletContext]
來給 EmbeddedViewRef
附加一個上下文對象。 [ngTemplateOutletContext]
是一個對象,該對象的 key 可在模板中使用 let
語句進行綁定。html
示例: <ng-container *ngTemplateOutlet="templateRefExp; context: contextExp"></ng-container> java
*我測試:<template> <ng-template> <ng-container> 均可承載這個指令, 但不支持<div>這樣的標籤,提示:segmentfault
使用目的
須要要自定義標題或頁腳的內容。
好比編寫彈窗組件,不建議在組件模板中寫死標題和頁腳的內容,當在頁面上使用該組件時, 頁面能夠動態向指定組件內佔位傳入「一些內容」,組件會把它們插入到它想要的地方!
好比ng-zorro中,也大量這樣用法,https://ng.ant.design/components/card/zh#components-card-demo-simple
api
2、組件自定義輸入內容
好比在一個頁面上,引用nz-card時,把主頁面上<ng-template>內容插入到nz-card中去,文檔上示例代碼:
app
若是要編寫這樣的組件,須要考慮幾個問題,框架
一、如何引用主頁面上的一個模板元素 ( 使用本地變量 # ,我記得之前官方文檔叫「局部模版變量"或「模板引用變量」)
二、引用的變量如何傳遞給子組件中 (子組件定義@Input ,使用類型爲TemplateRef<T>的變量接收)
三、子組件如何使用這個引用變量 ( 在模版中,用ngTemplateOutlet 綁定這個變量便可)
四、引用的模板元素從主頁面上來, 如何把子組件的數據(即子組件中上下文)傳遞給這個引用元素上來?(向ngTemplateOutlet 傳入 context: myContext」)函數
上下文傳遞很重要。組件爲了使用上的靈活,一部份內容定義在組件以外的(即主頁面上),當它插入到子組件中的時候,必然要顯示子組件內的一些數據,它纔有意義。
ngTemplateOutlet 不只用於綁定元素,還負責把子組件中的一個數據上下文傳遞進去.測試
五、模板元素如何使用上下文? (使用 模板輸入變量let
的形式,接收上下文屬性值,再用 {{ }}語法插入值)this
參考官方文檔:


完整而精巧的小例子,包含上面5個要點,要認真融會貫通呀:
源碼以下:
/// 主頁面 @Component({ selector: 'app-root', template: ` <h1>Angular's ngTemplateOutlet 完整示意-----我是主頁</h1> <app-content [dynamicRef]="usedByContent"></app-content> <ng-template #usedByContent let-name let-valueInContent=valueInContent> <div style="margin-left:20px;border:dashed;"> <div>Hello {{name}}! 組件內的上下文綁定: {{valueInContent}} ....</div> <div> 主頁面的變量綁定: {{valueInApp}}</div> </div> </ng-template>`, }) export class AppComponent { public valueInApp = "valueInApp :)"; } /// 一個子組件 @Component({ selector: 'app-content', template: ` <div>我是子組件,下面的內容是動態加載 :)</div> <template *ngTemplateOutlet="dynamicRef context: myContext"> </template> `, }) export class AppContent { display = false; @Input() dynamicRef: TemplateRef<HTMLDivElement>; myContext = { $implicit: 'World', valueInContent: "子組件內的value" }; }
3、ng-zorro 的組件的實現
支持TemplateRef參數的組件的實現
ng-zorro中,大量的組件的參數支持傳入模板引用,它的使用無處不在,要研究框架是如何實現的傳入模板引用的參數。
以nz-card爲例,它的API說明 :
凡是支持插入外部的模板引用的參數, 它們的類型就是: string|TemplateRef<void> 或者 TemplateRef<void>
翻看ng-zorro的nz-card 源碼,摘引以下,如今看它是否是超級簡單:
// 組件TS @Input() nzExtra: string | TemplateRef<void>; // 組件HTML <div class="ant-card-extra" *ngIf="nzExtra"> <ng-container *nzStringTemplateOutlet="nzExtra">{{ nzExtra }}</ng-container> </div>
// 組件TS @Input() nzActions: Array<TemplateRef<void>> = []; // 組件HTML <ul class="ant-card-actions" *ngIf="nzActions.length"> <li *ngFor="let action of nzActions"> <span><ng-template [ngTemplateOutlet]="action"></ng-template></span> </li> </ul>
對於TemplateRef<void>的參數, 直接用[ngTemplateOutlet] 來綁定便可。框架使用ngTemplateOutlet的方式和咱們上一節看到的同樣。
我測試 [ngTemplateOutlet] 不可綁定上下文,但*ngTemplateOutlet就能夠,因此咱們儘可能使用*的格式吧!
對於string|TemplateRef<void>的參數,用*nzStringTemplateOutlet 來綁定,這是什麼鬼東西呢?
*nzStringTemplateOutlet探險
*nzStringTemplateOutlet 是ng-zorro官方擴展的指令,很實用且複雜的指令,它即接受string|TemplateRef<void>的兩種參數值,插入到子組件的模板中去。
如今要當心翼翼的進入源碼探險之旅了!
2個知識必備:
一、構造函數注入變量,
viewContainer表明指令的宿主元素,此處它承擔渲染容器的做用,
defalultTemplate注入的是宿主元素的內容元素,就是上面的{{nzExtra}}部分。
// 注入viewContainer 宿主元素, 注入 defaultTemplate的 TemplateRef<void> constructor(private viewContainer: ViewContainerRef, private defaultTemplate: TemplateRef<void>) {}
爲何能這樣注入變量,這得參見官方文檔:
viewContainer注入:
defalultTemplate注入:
二、動態建立EmbeddedView, 這是動態組件時,常常要使用的函數!
this.viewContainer.createEmbeddedView( 模版引用,上下文數據 ); //返回一個EmbeddedView
分析源碼邏輯
使用nzStringTemplateOutlet的情景以下,nzExtra的值:string | TemplateRef<void> 兩種可能 :
<ng-container *nzStringTemplateOutlet="nzExtra">{{ nzExtra }}</ng-container>
一、首先得到各自的 TemplateRef<any> 對象。 (見上圖紅綠線)
參數爲:字符串string時, 經過注入, {{nzExtra}} ==> defalultTemplate.
參數爲:模板TemplateRef時, 經過指令賦值, 把 變量nzExtra==>inputTemplate.
二、建立EmeddedView。
使用viewContainer.createEmbeddedView方法, 把TemplateRef和上下文來生成View.
三、監聽指令ngOnChanges。
四、判斷是否從新建立視圖。由於用戶動態修改引用或上下文時,整個視圖要重建!
是否重建View,就是判斷nzStringTemplateOutletContext, nzStringTemplateOutlet 是否變化
a) 若是nzStringTemplateOutlet第一次賦值,或nzStringTemplateOutlet變化先後不都是string, 要重建視圖
b) 若是nzStringTemplateOutletContext 的屬性shape有變化時,也要重建視圖。
(注:新上下文的屬性比舊上下文屬性個數多時才變化, 若只是屬性值變化,是不用更新視圖)
五、若須要重建視圖,則重建視圖。
六、無需重建視圖,則進一步判斷是不是TemplateRef參數狀況,若是上下文數據變化了,動態更新EmeddedView的值!
經過以上源碼,nzStringTemplateOutlet指令就能夠監聽綁定模板變化,以及上下文數據變化,更新視圖。 正因爲這個指令強大,方便,咱們在使用ng-zorro的組件時,才倍感方便順手。
綁定上下文件的語法
最後,在ng-zorro源碼中,搜索 nzStringTemplateOutlet綁定上下文時的語法,引自nz-form-control源碼:
// ng-zorro的官方寫法 <ng-container *nzStringTemplateOutlet="nzSuccessTip;context:{$implicit:validateControl};"> {{ nzSuccessTip }} </ng-container>
忽然很意外,爲何不是下面這樣使用? 爲何寫context就能自動賦值到nzStringTemplateOutletContext上去??
可能這是因爲Angular編譯時,特殊的處理吧!
// 這個寫法編譯報錯,container 不存在nzStringTemplateOutletContext屬性 <ng-container *nzStringTemplateOutlet="nzSuccessTip" [nzStringTemplateOutletContext]="{$implicit:validateControl}「 > {{ nzSuccessTip }} </ng-container>
4、它的同類指令:NgComponentOutlet
偶然看到Common 模塊中,還有NgComponentOutlet指令,根據名稱也能猜出它的做用,直接把一個組件綁定過來,此時只要傳遞一個組件的類名便可!
我想起去年編寫實時數據看板的功能時,每個看板的子組件是根據配置,動態生成一個類的工廠函數,再建立類view,再插入到相應的containerview中去。 當時覺得很完美的方法,實際上是繞了不少彎路,直接用NgComponentOutlet一句話不就夠了嗎,當時真的蠢,不過看了許多文章,也沒有講到這個指令的地方。Angular的知識點太多太碎了呀!