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 相关文章推荐
document对象execCommand的command参数介绍
Aug 01 Javascript
滚动经典最新话题[prototype框架]下编写
Oct 03 Javascript
javascript 关于# 和 void的区别分析
Oct 26 Javascript
JSQL SQLProxy 的 php 版本代码
May 05 Javascript
jquery uaMatch源代码
Feb 14 Javascript
解决3.01版的jquery.form.js中文乱码问题的解决方法
Mar 08 Javascript
jQuery阻止同类型事件小结
Apr 19 Javascript
通过一段代码简单说js中的this的使用
Jul 23 Javascript
Javascript实现时间倒计时效果
Jul 15 Javascript
基于Bootstrap表单验证功能
Nov 17 Javascript
JavaScript引用类型Date常见用法实例分析
Aug 08 Javascript
让mocha支持ES6模块的方法实现
Jan 14 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中用PHPMailer来发送邮件的示例(126.com的例子)
2013/06/24 PHP
PHP采用超长(超大)数字运算防止数字以科学计数法显示的方法
2016/04/01 PHP
Laravel 模型关联基础教程详解
2019/09/17 PHP
Thinkphp框架使用list_to_tree 实现无限级分类列出所有节点示例
2020/04/04 PHP
防止动态加载JavaScript引起的内存泄漏问题
2009/10/08 Javascript
JQuery扩展插件Validate 5添加自定义验证方法
2011/09/05 Javascript
Javascript Objects详解
2014/09/04 Javascript
JavaScript模拟实现键盘打字效果
2015/06/29 Javascript
IScroll5 中文API参数说明和调用方法
2016/05/21 Javascript
jQuery中实现prop()函数控制多选框(全选,反选)
2016/08/19 Javascript
微信小程序 开发工具快捷键整理
2016/10/31 Javascript
jquery replace方法去空格
2017/05/08 jQuery
Bootstrap多级菜单的实现代码
2017/05/23 Javascript
vue自定义底部导航栏Tabbar的实现代码
2018/09/03 Javascript
layui表单提交到后台自动封装到实体类的方法
2019/09/12 Javascript
layer.open组件获取弹出层页面变量、函数的实例
2019/09/25 Javascript
解决vue项目input输入框双向绑定数据不实时生效问题
2020/08/05 Javascript
解决Can't find variable: SockJS vue项目的问题
2020/09/22 Javascript
Python实现简易端口扫描器代码实例
2017/03/15 Python
Python中的id()函数指的什么
2017/10/17 Python
Python实现将字符串的首字母变为大写,其余都变为小写的方法
2019/06/11 Python
Python模块_PyLibTiff读取tif文件的实例
2020/01/13 Python
Python3 pickle对象串行化代码实例解析
2020/03/23 Python
Python爬虫设置Cookie解决网站拦截并爬取蚂蚁短租的问题
2021/02/22 Python
前端实现打印图像功能
2019/08/27 HTML / CSS
Html5写一个简单的俄罗斯方块小游戏
2019/12/03 HTML / CSS
Harrods英国:世界领先的奢侈品百货商店
2020/09/23 全球购物
求两个数的乘积和商数,该作用由宏定义来实现
2013/03/13 面试题
历史学专业毕业生求职信
2013/09/27 职场文书
学习党章思想汇报
2014/01/07 职场文书
医学生个人求职信范文
2014/02/07 职场文书
批评与自我批评材料
2014/02/15 职场文书
宿舍违规用电检讨书
2014/02/16 职场文书
《最后的姿势》教学反思
2014/02/27 职场文书
环保建议书300字
2014/05/14 职场文书
Nginx四层负载均衡的配置指南
2021/06/11 Servers