Angular2學(xué)習(xí)教程之TemplateRef和ViewContainerRef詳解
TemplateRef
在介紹 TemplateRef 前,我們先來(lái)了解一下 HTML 模板元素 - <template> 。模板元素是一種機(jī)制,允許包含加載頁(yè)面時(shí)不渲染,但又可以隨后通過(guò) JavaScript 進(jìn)行實(shí)例化的客戶端內(nèi)容。我們可以將模板視作為存儲(chǔ)在頁(yè)面上稍后使用的一小段內(nèi)容。
在 HTML5 標(biāo)準(zhǔn)引入 template 模板元素之前,我們都是使用 <script> 標(biāo)簽進(jìn)行客戶端模板的定義,具體如下:
<script id="tpl-mock" type="text/template"> <span>I am span in mock template</span> </script>
對(duì)于支持 HTML5 template 模板元素的瀏覽器,我們可以這樣創(chuàng)建客戶端模板:
<template id="tpl"> <span>I am span in template</span> </template>
下面我們來(lái)看一下 HTML5 template 模板元素的使用示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"> <title>HTML5 Template Element Demo</title></head>
<body>
<h4>HTML5 Template Element Demo</h4>
<!-- Template Container -->
<div class="tpl-container"></div>
<!-- Template -->
<template id="tpl">
<span>I am span in template</span>
</template>
<!-- Script -->
<script type="text/javascript">
(function renderTpl() {
if ('content' in document.createElement('template')) {
var tpl = document.querySelector('#tpl');
var tplContainer = document.querySelector('.tpl-container');
var tplNode = document.importNode(tpl.content, true);
tplContainer.appendChild(tplNode);
} else {
throw new Error("Current browser doesn't support template element");
}
})();
</script>
</body>
</html>
以上代碼運(yùn)行后,在瀏覽器中我們會(huì)看到以下內(nèi)容:
HTML5 Template Element Demo I am span in template
而當(dāng)我們注釋掉 tplContainer.appendChild(tplNode) 語(yǔ)句時(shí),刷新瀏覽器后看到的是:
HTML5 Template Element Demo
這說(shuō)明頁(yè)面中 <template> 模板元素中的內(nèi)容,如果沒(méi)有進(jìn)行處理對(duì)用戶來(lái)說(shuō)是不可見(jiàn)的。Angular 2 中,<template> 模板元素主要應(yīng)用在結(jié)構(gòu)指令中,接下來(lái)我們先來(lái)介紹一下本文中的第一個(gè)主角 - TemplateRef:
import {Component, TemplateRef, ViewChild, AfterViewInit} from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>Welcome to Angular World</h1>
<template #tpl>
<span>I am span in template</span>
</template>
`,
})
export class AppComponent {
name: string = 'Semlinker';
@ViewChild('tpl')
tpl: TemplateRef<any>;
ngAfterViewInit() {
console.dir(this.tpl);
}
}
上述代碼運(yùn)行后的控制臺(tái)的輸出結(jié)果如下:

從上圖中,我們發(fā)現(xiàn) @Component template 中定義的 <template> 模板元素,渲染后被替換成 comment 元素,其內(nèi)容為 "template bindings={}" 。此外我們通過(guò) @ViewChild 獲取的模板元素,是 TemplateRef_ 類的實(shí)例,接下來(lái)我們來(lái)研究一下 TemplateRef_ 類:
TemplateRef_
// @angular/core/src/linker/template_ref.d.ts
export declare class TemplateRef_<C> extends TemplateRef<C> {
private _parentView;
private _nodeIndex;
private _nativeElement;
constructor(_parentView: AppView<any>, _nodeIndex: number, _nativeElement: any);
createEmbeddedView(context: C): EmbeddedViewRef<C>;
elementRef: ElementRef;
}
TemplateRef
// @angular/core/src/linker/template_ref.d.ts
// 用于表示內(nèi)嵌的template模板,能夠用于創(chuàng)建內(nèi)嵌視圖(Embedded Views)
export declare abstract class TemplateRef<C> {
elementRef: ElementRef;
abstract createEmbeddedView(context: C): EmbeddedViewRef<C>;
}
(備注:抽象類與普通類的區(qū)別是抽象類有包含抽象方法,不能直接實(shí)例化抽象類,只能實(shí)例化該抽象類的子類)
我們已經(jīng)知道 <template> 模板元素,渲染后被替換成 comment 元素,那么應(yīng)該如何顯示我們模板中定義的內(nèi)容呢 ?我們注意到了 TemplateRef 抽象類中定義的 createEmbeddedView抽象方法,該方法的返回值是 EmbeddedViewRef 對(duì)象。那好我們馬上來(lái)試一下:
import {Component, TemplateRef, ViewChild, AfterViewInit} from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>Welcome to Angular World</h1>
<template #tpl>
<span>I am span in template</span>
</template>
`,
})
export class AppComponent {
name: string = 'Semlinker';
@ViewChild('tpl')
tpl: TemplateRef<any>;
ngAfterViewInit() {
let embeddedView = this.tpl.createEmbeddedView(null);
console.dir(embeddedView);
}
}
上述代碼運(yùn)行后的控制臺(tái)的輸出結(jié)果如下:

從圖中我們可以知道,當(dāng)調(diào)用 createEmbeddedView 方法后返回了 ViewRef_ 視圖對(duì)象。該視圖對(duì)象的 rootNodes 屬性包含了 <template> 模板中的內(nèi)容。在上面的例子中,我們知道了 TemplateRef 實(shí)例對(duì)象中的 elementRef 屬性封裝了我們的 comment 元素,那么我們可以通過(guò) insertBefore 方法來(lái)創(chuàng)建我們模板中定義的內(nèi)容。
import { Component, TemplateRef, ViewChild, AfterViewInit } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>Welcome to Angular World</h1>
<template #tpl>
<span>I am span in template {{name}}</span>
</template>
`,
})
export class AppComponent {
name: string = 'Semlinker';
@ViewChild('tpl')
tpl: TemplateRef<any>;
ngAfterViewInit() {
// 頁(yè)面中的<!--template bindings={}-->元素
let commentElement = this.tpl.elementRef.nativeElement;
// 創(chuàng)建內(nèi)嵌視圖
let embeddedView = this.tpl.createEmbeddedView(null);
// 動(dòng)態(tài)添加子節(jié)點(diǎn)
embeddedView.rootNodes.forEach((node) => {
commentElement.parentNode
.insertBefore(node, commentElement.nextSibling);
});
}
}
成功運(yùn)行上面的代碼后,在瀏覽器中我們會(huì)看到以下內(nèi)容:
Welcome to Angular World I am span in template
現(xiàn)在我們來(lái)回顧一下,上面的處理步驟:
- 創(chuàng)建內(nèi)嵌視圖(embedded view)
- 遍歷內(nèi)嵌視圖中的 rootNodes,動(dòng)態(tài)的插入 node
雖然我們已經(jīng)成功的顯示出 template 模板元素中的內(nèi)容,但發(fā)現(xiàn)整個(gè)流程還是太復(fù)雜了,那有沒(méi)有簡(jiǎn)單地方式呢 ?是時(shí)候介紹本文中第二個(gè)主角 - ViewContainerRef。
ViewContainerRef
我們先來(lái)檢驗(yàn)一下它的能力,然后再來(lái)好好地分析它。具體示例如下:
import { Component, TemplateRef, ViewChild, ViewContainerRef, AfterViewInit } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>Welcome to Angular World</h1>
<template #tpl>
<span>I am span in template</span>
</template>
`,
})
export class AppComponent {
name: string = 'Semlinker';
@ViewChild('tpl')
tplRef: TemplateRef<any>;
@ViewChild('tpl', { read: ViewContainerRef })
tplVcRef: ViewContainerRef;
ngAfterViewInit() {
// console.dir(this.tplVcRef); (1)
this.tplVcRef.createEmbeddedView(this.tplRef);
}
}
移除上面代碼中的注釋,即可在控制臺(tái)看到以下的輸出信息:

而在瀏覽器中我們會(huì)看到以下內(nèi)容:
Welcome to Angular World I am span in template
接下來(lái)我們來(lái)看一下 ViewContainerRef_ 類:
// @angular/core/src/linker/view_container_ref.d.ts
// 用于表示一個(gè)視圖容器,可添加一個(gè)或多個(gè)視圖
export declare class ViewContainerRef_ implements ViewContainerRef {
...
length: number; // 返回視圖容器中已存在的視圖個(gè)數(shù)
element: ElementRef;
injector: Injector;
parentInjector: Injector;
// 基于TemplateRef創(chuàng)建內(nèi)嵌視圖,并自動(dòng)添加到視圖容器中,可通過(guò)index設(shè)置
// 視圖添加的位置
createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C,
index?: number): EmbeddedViewRef<C>;
// 基 ComponentFactory創(chuàng)建組件視圖
createComponent<C>(componentFactory: ComponentFactory<C>,
index?: number, injector?: Injector, projectableNodes?: any[][]): ComponentRef<C>;
insert(viewRef: ViewRef, index?: number): ViewRef;
move(viewRef: ViewRef, currentIndex: number): ViewRef;
indexOf(viewRef: ViewRef): number;
remove(index?: number): void;
detach(index?: number): ViewRef;
clear(): void;
}
通過(guò)源碼我們可以知道通過(guò) ViewContainerRef_ 實(shí)例,我們可以方便地操作視圖,也可以方便地基于 TemplateRef 創(chuàng)建視圖?,F(xiàn)在我們來(lái)總結(jié)一下 TemplateRef 與 ViewContainerRef。
TemplateRef:用于表示內(nèi)嵌的 template 模板元素,通過(guò) TemplateRef 實(shí)例,我們可以方便創(chuàng)建內(nèi)嵌視圖(Embedded Views),且可以輕松地訪問(wèn)到通過(guò) ElementRef 封裝后的 nativeElement。需要注意的是組件視圖中的 template 模板元素,經(jīng)過(guò)渲染后會(huì)被替換成 comment 元素。
ViewContainerRef:用于表示一個(gè)視圖容器,可添加一個(gè)或多個(gè)視圖。通過(guò) ViewContainerRef 實(shí)例,我們可以基于 TemplateRef 實(shí)例創(chuàng)建內(nèi)嵌視圖,并能指定內(nèi)嵌視圖的插入位置,也可以方便對(duì)視圖容器中已有的視圖進(jìn)行管理。簡(jiǎn)而言之,ViewContainerRef 的主要作用是創(chuàng)建和管理內(nèi)嵌視圖或組件視圖。
我有話說(shuō)
1.Angular 2 支持的 View(視圖) 類型有哪幾種 ?
- Embedded Views - Template 模板元素
- Host Views - Component 組件
1.1 如何創(chuàng)建 Embedded View
ngAfterViewInit() {
let view = this.tpl.createEmbeddedView(null);
}
1.2 如何創(chuàng)建 Host View
constructor(private injector: Injector,
private r: ComponentFactoryResolver) {
let factory = this.r.resolveComponentFactory(AppComponent);
let componentRef = factory.create(injector);
let view = componentRef.hostView;
}
2.Angular 2 Component 組件中定義的 <template> 模板元素為什么渲染后會(huì)被移除 ?
因?yàn)?<template> 模板元素,已經(jīng)被 Angular 2 解析并封裝成 TemplateRef 實(shí)例,通過(guò) TemplateRef 實(shí)例,我們可以方便地創(chuàng)建內(nèi)嵌視圖(Embedded View),我們不需要像開(kāi)篇中的例子那樣,手動(dòng)操作 <template> 模板元素。
3.ViewRef 與 EmbeddedViewRef 之間有什么關(guān)系 ?
ViewRef 用于表示 Angular View(視圖),視圖是可視化的 UI 界面。EmbeddedViewRef 繼承于 ViewRef,用于表示 <template> 模板元素中定義的 UI 元素。
ViewRef
// @angular/core/src/linker/view_ref.d.ts
export declare abstract class ViewRef {
destroyed: boolean;
abstract onDestroy(callback: Function): any;
}
EmbeddedViewRef
// @angular/core/src/linker/view_ref.d.ts
export declare abstract class EmbeddedViewRef<C> extends ViewRef {
context: C;
rootNodes: any[]; // 保存<template>模板中定義的元素
abstract destroy(): void; // 用于銷(xiāo)毀視圖
}
總結(jié)
Angular 2 中 TemplateRef 與 ViewContainerRef 的概念對(duì)于初學(xué)者來(lái)說(shuō)會(huì)比較羞澀難懂,本文從基本的 HTML 5 <template> 模板元素開(kāi)始,介紹了如何操作和應(yīng)用頁(yè)面中定義的模板。然后通過(guò)實(shí)例介紹了 Angular 2 中 TemplateRef 和 ViewContainerRef 的定義和作用。希望通過(guò)這篇文章,讀者能更好的理解 TemplateRef 與 ViewContainerRef。
好了,以上就是這篇文章的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
AngularJS基礎(chǔ) ng-paste 指令簡(jiǎn)單示例
本文主要介紹AngularJS ng-paste 指令,這里對(duì)ng-paste 指令的基礎(chǔ)資料做了整理,并附有代碼示例,有需要的朋友可以參考下2016-08-08
利用Angularjs和原生JS分別實(shí)現(xiàn)動(dòng)態(tài)效果的輸入框
現(xiàn)在的很多網(wǎng)站都將輸入框做成了動(dòng)態(tài)的效果,這樣對(duì)于用戶體檢來(lái)說(shuō)非常好,這篇文章分別用Angularjs和原生JS兩種方法來(lái)實(shí)現(xiàn)動(dòng)態(tài)效果的輸入框,具有一定的參考價(jià)值,有需要的小伙伴們可以來(lái)參考借鑒。2016-09-09
AngularJs 60分鐘入門(mén)基礎(chǔ)教程
AngularJs是一個(gè)不錯(cuò)的用于開(kāi)發(fā)SPA應(yīng)用(單頁(yè)Web應(yīng)用)的框架。通過(guò)本文給大家介紹angularjs基礎(chǔ)教程,需要的朋友要求學(xué)習(xí)吧2016-04-04
使用Angular.js開(kāi)發(fā)的注意事項(xiàng)
這篇文章主要記錄了一些在學(xué)習(xí)和使用angular.js踩到的坑和需要注意的點(diǎn),方便以后自己查閱,也給同樣遇到這些問(wèn)題的朋友們一些幫助,有需要的朋友們下面來(lái)一起看看吧。2016-10-10
AngularJS基于ui-route實(shí)現(xiàn)深層路由的方法【路由嵌套】
這篇文章主要介紹了AngularJS基于ui-route實(shí)現(xiàn)深層路由的方法,涉及AngularJS路由嵌套操作相關(guān)實(shí)現(xiàn)步驟與技巧,需要的朋友可以參考下2016-12-12
AngularJS全局scope與Isolate scope通信用法示例
這篇文章主要介紹了AngularJS全局scope與Isolate scope通信用法,結(jié)合格式分析了全局scope和directive本地scope相關(guān)功能、通信用法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2016-11-11
Angular應(yīng)用prerender預(yù)渲染提高頁(yè)面加載速度
這篇文章主要介紹了Angular應(yīng)用prerender預(yù)渲染提高頁(yè)面加載速度,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10

