Angular 2 TemplateRef & ViewContainerRef
TemplateRef在介绍 TemplateRef 前,我们先来了解一下 HTML 模板元素 - <template> 。模板元素是一种机制,允许包含加载页面时不渲染,但又可以随后通过 JavaScript 进行实例化的客户端内容。我们可以将模板视作为存储在页面上稍后使用的一小段内容。 在 HTML5 标准引入 template 模板元素之前,我们都是使用 <script> 标签进行客户端模板的定义,具体如下: <script id="tpl-mock" type="text/template"> <span>I am span in mock template</span> </script> 对于支持 HTML5 template 模板元素的浏览器,我们可以这样创建客户端模板: <template id="tpl"> <span>I am span in template</span> </template> 下面我们来看一下 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> 以上代码运行后,在浏览器中我们会看到以下内容: HTML5 Template Element Demo I am span in template 而当我们注释掉 tplContainer.appendChild(tplNode) 语句时,刷新浏览器后看到的是: HTML5 Template Element Demo 这说明页面中 <template> 模板元素中的内容,如果没有进行处理对用户来说是不可见的。Angular 2 中,<template> 模板元素主要应用在结构指令中,此外在 Angular 2 属性指令 vs 结构指令 文章中我们也介绍了 <template> 模板元素和自定义结构指令,接下来我们先来介绍一下本文中的第一个主角 - 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); } } 上述代码运行后的控制台的输出结果如下:
从上图中,我们发现 @Component template 中定义的 <template> 模板元素,渲染后被替换成 comment 元素,其内容为 "template bindings={}" 。此外我们通过 @ViewChild 获取的模板元素,是 TemplateRef_ 类的实例,接下来我们来研究一下 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 // 用于表示内嵌的template模板,能够用于创建内嵌视图(Embedded Views) export declare abstract class TemplateRef<C> { elementRef: ElementRef; abstract createEmbeddedView(context: C): EmbeddedViewRef<C>; } (备注:抽象类与普通类的区别是抽象类有包含抽象方法,不能直接实例化抽象类,只能实例化该抽象类的子类) 我们已经知道 <template> 模板元素,渲染后被替换成 comment 元素,那么应该如何显示我们模板中定义的内容呢 ?我们注意到了 TemplateRef 抽象类中定义的 createEmbeddedView import {Component,}) export class AppComponent { name: string = 'Semlinker'; @ViewChild('tpl') tpl: TemplateRef<any>; ngAfterViewInit() { let embeddedView = this.tpl.createEmbeddedView(null); console.dir(embeddedView); } } 上述代码运行后的控制台的输出结果如下:
从图中我们可以知道,当调用 createEmbeddedView 方法后返回了 ViewRef_ 视图对象。该视图对象的 rootNodes 属性包含了 <template> 模板中的内容。在上面的例子中,我们知道了 TemplateRef 实例对象中的 elementRef 属性封装了我们的 comment 元素,那么我们可以通过 insertBefore 方法来创建我们模板中定义的内容。 import { Component,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() { // 页面中的<!--template bindings={}-->元素 let commentElement = this.tpl.elementRef.nativeElement; // 创建内嵌视图 let embeddedView = this.tpl.createEmbeddedView(null); // 动态添加子节点 embeddedView.rootNodes.forEach((node) => { commentElement.parentNode .insertBefore(node,commentElement.nextSibling); }); } } 成功运行上面的代码后,在浏览器中我们会看到以下内容: Welcome to Angular World I am span in template 现在我们来回顾一下,上面的处理步骤:
虽然我们已经成功的显示出 template 模板元素中的内容,但发现整个流程还是太复杂了,那有没有简单地方式呢 ?是时候介绍本文中第二个主角 - ViewContainerRef。 ViewContainerRef我们先来检验一下它的能力,然后再来好好地分析它。具体示例如下: import { Component,ViewContainerRef,}) 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); } } 移除上面代码中的注释,即可在控制台看到以下的输出信息:
而在浏览器中我们会看到以下内容: Welcome to Angular World I am span in template 接下来我们来看一下 ViewContainerRef_ 类: // @angular/core/src/linker/view_container_ref.d.ts // 用于表示一个视图容器,可添加一个或多个视图 export declare class ViewContainerRef_ implements ViewContainerRef { ... length: number; // 返回视图容器中已存在的视图个数 element: ElementRef; injector: Injector; parentInjector: Injector; // 基于TemplateRef创建内嵌视图,并自动添加到视图容器中,可通过index设置 // 视图添加的位置 createEmbeddedView<C>(templateRef: TemplateRef<C>,context?: C,index?: number): EmbeddedViewRef<C>; // 基 ComponentFactory创建组件视图 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; } 通过源码我们可以知道通过 ViewContainerRef_ 实例,我们可以方便地操作视图,也可以方便地基于 TemplateRef 创建视图。现在我们来总结一下 TemplateRef 与 ViewContainerRef。 TemplateRef:用于表示内嵌的 template 模板元素,通过 TemplateRef 实例,我们可以方便创建内嵌视图(Embedded Views),且可以轻松地访问到通过 ElementRef 封装后的 nativeElement。需要注意的是组件视图中的 template 模板元素,经过渲染后会被替换成 comment 元素。 ViewContainerRef:用于表示一个视图容器,可添加一个或多个视图。通过 ViewContainer 我有话说1.Angular 2 支持的 View(视图) 类型有哪几种 ?
1.1 如何创建 Embedded View ngAfterViewInit() { let view = this.tpl.createEmbeddedView(null); } 1.2 如何创建 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> 模板元素为什么渲染后会被移除 ? 因为 <template> 模板元素,已经被 Angular 2 解析并封装成 TemplateRef 实例,通过 TemplateRef 实例,我们可以方便地创建内嵌视图(Embedded View),我们不需要像开篇中的例子那样,手动操作 <template> 模板元素。 3.ViewRef 与 EmbeddedViewRef 之间有什么关系 ? 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; // 用于销毁视图 } 总结Angular 2 中 TemplateRef 与 ViewContainerRef 的概念对于初学者来说会比较羞涩难懂,本文从基本的 HTML 5 <template> 模板元素开始,介绍了如何操作和应用页面中定义的模板。然后通过实例介绍了 Angular 2 中 TemplateRef 和 ViewContainerRef 的定义和作用。希望通过这篇文章,读者能更好的理解 TemplateRef 与 ViewContainerRef。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |