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 相关文章推荐
AJAX异步从优酷专辑中采集所有视频及信息(JavaScript代码)
Nov 20 Javascript
js ajaxfileupload.js上传报错的解决方法
May 05 Javascript
Json解析的方法小结
Jun 22 Javascript
ionic进入多级目录后隐藏底部导航栏(tabs)的完美解决方案
Nov 23 Javascript
Bootstrap Table使用整理(一)
Jun 09 Javascript
Vue.extend构造器的详解
Jul 17 Javascript
jQuery常用选择器详解
Jul 17 jQuery
Angular4学习笔记之新建项目的方法
Jul 18 Javascript
JS与HTML结合实现流程进度展示条思路详解
Sep 03 Javascript
浅谈react-router HashRouter和BrowserRouter的使用
Dec 29 Javascript
JS Object.preventExtensions(),Object.seal()与Object.freeze()用法实例分析
Aug 25 Javascript
利用Vue的v-for和v-bind实现列表颜色切换
Jul 17 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版微信小店调用api示例代码
2016/11/12 PHP
PHP的RSA加密解密方法以及开发接口使用
2018/02/11 PHP
List the Stored Procedures in a SQL Server database
2007/06/20 Javascript
用JQuery调用Session的实现代码
2010/10/29 Javascript
Extjs gridpanel 出现横向滚动条问题的解决方法
2011/07/04 Javascript
jquery validate poshytip 自定义样式
2012/11/26 Javascript
jQuery产品间断向下滚动效果核心代码
2014/05/08 Javascript
js实现刷新iframe的方法汇总
2015/04/27 Javascript
JS中产生标识符方式的演变
2015/06/12 Javascript
详解AngularJS中module模块的导入导出
2015/12/10 Javascript
Node.js静态文件服务器改进版
2016/01/10 Javascript
JavaScript中的this,call,apply使用及区别详解
2016/01/29 Javascript
微信js-sdk分享功能接口常用逻辑封装示例
2016/10/13 Javascript
html中鼠标滚轮事件onmousewheel的处理方法
2016/11/11 Javascript
基于代数方程库Algebra.js解二元一次方程功能示例
2017/06/09 Javascript
前端必备插件之纯原生JS的瀑布流插件Macy.js
2017/11/22 Javascript
Vue实现带进度条的文件拖动上传功能
2018/02/23 Javascript
React学习笔记之高阶组件应用
2018/06/02 Javascript
vue-router两种模式区别及使用注意事项详解
2019/08/01 Javascript
vue中实现点击变成全屏的多种方法
2020/09/27 Javascript
vue解决跨域问题(推荐)
2020/11/10 Javascript
[49:29]LGD vs Winstrike 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/18 DOTA
Python通过DOM和SAX方式解析XML的应用实例分享
2015/11/16 Python
DataFrame中的object转换成float的方法
2018/04/10 Python
python实现决策树分类(2)
2018/08/30 Python
Numpy数组array和矩阵matrix转换方法
2019/08/05 Python
Python数据可视化:幂律分布实例详解
2019/12/07 Python
python异常处理try except过程解析
2020/02/03 Python
python with (as)语句实例详解
2020/02/04 Python
Python常用编译器原理及特点解析
2020/03/23 Python
Pycharm 如何一键加引号的方法步骤
2021/02/05 Python
教师新年寄语
2014/04/03 职场文书
年度考核登记表个人总结
2015/03/06 职场文书
学生会干部任命书
2015/09/21 职场文书
MySQL 如何设计统计数据表
2021/06/15 MySQL
JavaScript中MutationObServer监听DOM元素详情
2021/11/27 Javascript