Angular2学习教程之TemplateRef和ViewContainerRef详解


Posted in Javascript onMay 25, 2017

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> 模板元素主要应用在结构指令中,接下来我们先来介绍一下本文中的第一个主角 - 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);
 }
}

上述代码运行后的控制台的输出结果如下:

Angular2学习教程之TemplateRef和ViewContainerRef详解

从上图中,我们发现 @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抽象方法,该方法的返回值是 EmbeddedViewRef 对象。那好我们马上来试一下:

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);
 }
}

上述代码运行后的控制台的输出结果如下:

Angular2学习教程之TemplateRef和ViewContainerRef详解

从图中我们可以知道,当调用 createEmbeddedView 方法后返回了 ViewRef_ 视图对象。该视图对象的 rootNodes 属性包含了 <template> 模板中的内容。在上面的例子中,我们知道了 TemplateRef 实例对象中的 elementRef 属性封装了我们的 comment 元素,那么我们可以通过 insertBefore 方法来创建我们模板中定义的内容。

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() {
 // 页面中的<!--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

现在我们来回顾一下,上面的处理步骤:

  • 创建内嵌视图(embedded view)
  • 遍历内嵌视图中的 rootNodes,动态的插入 node

虽然我们已经成功的显示出 template 模板元素中的内容,但发现整个流程还是太复杂了,那有没有简单地方式呢 ?是时候介绍本文中第二个主角 - ViewContainerRef。

ViewContainerRef

我们先来检验一下它的能力,然后再来好好地分析它。具体示例如下:

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);
 }
}

移除上面代码中的注释,即可在控制台看到以下的输出信息:

Angular2学习教程之TemplateRef和ViewContainerRef详解

而在浏览器中我们会看到以下内容:

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:用于表示一个视图容器,可添加一个或多个视图。通过 ViewContainerRef 实例,我们可以基于 TemplateRef 实例创建内嵌视图,并能指定内嵌视图的插入位置,也可以方便对视图容器中已有的视图进行管理。简而言之,ViewContainerRef 的主要作用是创建和管理内嵌视图或组件视图。

我有话说

1.Angular 2 支持的 View(视图) 类型有哪几种 ?

  • Embedded Views - Template 模板元素
  • Host Views - Component 组件

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。

好了,以上就是这篇文章的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
jquery关于页面焦点的定位(文本框获取焦点时改变样式 )
Sep 10 Javascript
以JSON形式将JS中Array对象数组传至后台的方法
Jan 06 Javascript
js日期对象兼容性的处理方法
Jan 28 Javascript
js比较日期大小的方法
May 12 Javascript
jQuery Form插件使用详解_动力节点Java学院整理
Jul 17 jQuery
详解vue填坑之解决部分浏览器不支持pushState方法
Jul 12 Javascript
JavaScript根据json生成html表格的示例代码
Oct 24 Javascript
微信小程序websocket聊天室的实现示例代码
Feb 12 Javascript
深入浅析golang zap 日志库使用(含文件切割、分级别存储和全局使用等)
Feb 19 Javascript
Vue实现可移动水平时间轴
Jun 29 Javascript
vue 点击其他区域关闭自定义div操作
Jul 17 Javascript
浅谈vue-props的default写不写有什么区别
Aug 09 Javascript
Bootstrap组件之下拉菜单,多级菜单及按钮布局方法实例
May 25 #Javascript
vue-router实现webApp切换页面动画效果代码
May 25 #Javascript
Angular 2.x学习教程之结构指令详解
May 25 #Javascript
bootstrap动态添加面包屑(breadcrumb)及其响应事件的方法
May 25 #Javascript
js获取一组日期中最近连续的天数
May 25 #Javascript
AngularJs定时器$interval 和 $timeout详解
May 25 #Javascript
slideToggle+slideup实现手机端折叠菜单效果
May 25 #Javascript
You might like
PHP跳转页面的几种实现方法详解
2013/06/08 PHP
ThinkPHP实现转换数据库查询结果数据到对应类型的方法
2017/11/16 PHP
JavaScript 精粹读书笔记(1,2)
2010/02/07 Javascript
JavaScript 原型学习总结
2010/10/29 Javascript
json数据与字符串的相互转化示例
2013/09/18 Javascript
document.getElementById获取控件对象为空的解决方法
2013/11/20 Javascript
javascript中的Base64、UTF8编码与解码详解
2015/03/18 Javascript
jQuery实现可用于博客的动态滑动菜单完整实例
2015/09/17 Javascript
详解js跨域原理以及2种解决方案
2015/12/09 Javascript
JS实现兼容各种浏览器的高级拖动方法完整实例【测试可用】
2016/06/21 Javascript
20分钟打造属于你的Bootstrap站点
2016/07/27 Javascript
jquery根据td给相同tr下其他td赋值的实现方法
2016/10/05 Javascript
jQuery 遍历map()方法详解
2016/11/04 Javascript
DataTables+BootStrap组合使用Ajax来获取数据并且动态加载dom的方法(排序,过滤,分页等)
2016/11/09 Javascript
Omi v1.0.2发布正式支持传递javascript表达式
2017/03/21 Javascript
JavaScript之DOM插入更新删除_动力节点Java学院整理
2017/07/03 Javascript
vue.js图片转Base64上传图片并预览的实现方法
2018/08/02 Javascript
node.js使用stream模块实现自定义流示例
2020/02/13 Javascript
vue 使用插槽分发内容操作示例【单个插槽、具名插槽、作用域插槽】
2020/03/06 Javascript
JS获取当前时间戳方法解析
2020/08/29 Javascript
python Selenium爬取内容并存储至MySQL数据库的实现代码
2017/03/16 Python
对python 判断数字是否小于0的方法详解
2019/01/26 Python
python 接口实现 供第三方调用的例子
2019/08/13 Python
python实现静态服务器
2019/09/05 Python
Python获取统计自己的qq群成员信息的方法
2019/11/15 Python
python实现扫雷游戏
2020/03/03 Python
CSS3 translate导致字体模糊的实例代码
2019/08/30 HTML / CSS
移动端HTML5开发神器之vconsole详解
2020/12/15 HTML / CSS
德国的大型美妆个护电商:Flaconi
2020/06/26 全球购物
优秀毕业生求职信
2014/06/05 职场文书
中秋节国旗下演讲稿
2014/09/05 职场文书
个人作风建设剖析材料
2014/10/11 职场文书
运动会800米赞词
2015/07/22 职场文书
2016年感恩节寄语
2015/12/07 职场文书
2019入党申请书范文3篇
2019/08/21 职场文书
SpringMVC 整合SSM框架详解
2021/08/30 Java/Android