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 相关文章推荐
javascript 面向对象编程 聊聊对象的事
Sep 17 Javascript
JQuery防止退格键网页后退的实现代码
Mar 23 Javascript
基于JQuery 滑动与动画的说明介绍
Apr 18 Javascript
jQuery实现鼠标滑过Div层背景变颜色的方法
Feb 17 Javascript
学习JavaScript设计模式(链式调用)
Nov 26 Javascript
vue模板语法-插值详解
Mar 06 Javascript
ES6中Math对象新增的方法实例详解
Apr 25 Javascript
Vue动态组件实例解析
Aug 20 Javascript
mui back 返回刷新页面的实例
Dec 06 Javascript
JavaScript实现五子棋游戏的方法详解
Jul 08 Javascript
Nuxt使用Vuex的方法示例
Sep 06 Javascript
JS前端宏任务微任务及Event Loop使用详解
Jul 23 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相关资料
2006/10/09 PHP
PHP set_error_handler()函数使用详解(示例)
2013/11/12 PHP
PHP串行化与反串行化实例分析
2016/12/27 PHP
Jquery插件之多图片异步上传
2010/10/20 Javascript
javascript实用小函数使用介绍
2013/11/11 Javascript
node.js中的buffer.write方法使用说明
2014/12/10 Javascript
jQuery子窗体取得父窗体元素的方法
2015/05/11 Javascript
jquery实现页面常用的返回顶部效果
2016/03/04 Javascript
JavaScript ES6中CLASS的使用详解
2016/11/22 Javascript
bootstrap响应式导航条模板使用详解(含下拉菜单,弹出框)
2017/11/17 Javascript
JavaScript&quot;模拟事件&quot;的注意要点详解
2019/02/13 Javascript
vue3.0中的双向数据绑定方法及优缺点
2019/08/01 Javascript
js中offset,client , scroll 三大元素知识点总结
2019/09/11 Javascript
JS算法教程之字符串去重与字符串反转
2020/12/15 Javascript
[58:09]Spirit vs NB Supermajor小组赛 A组败者组决赛 BO3 第三场 6.2
2018/06/03 DOTA
python基础教程之Hello World!
2014/08/29 Python
python3音乐播放器简单实现代码
2020/04/20 Python
利用python生成一个导出数据库的bat脚本文件的方法
2016/12/30 Python
python通过zabbix api获取主机
2018/09/17 Python
python开头的coding设置方法
2019/08/08 Python
python运用sklearn实现KNN分类算法
2019/10/16 Python
在TensorFlow中屏蔽warning的方式
2020/02/04 Python
pandas参数设置的实用小技巧
2020/08/23 Python
一款纯css3实现的圆形旋转分享按钮旋转角度可自己调整
2014/09/02 HTML / CSS
Smallable英国家庭概念店:设计师童装及家居装饰
2017/07/05 全球购物
阿联酋航空官方网站:Emirates
2017/10/17 全球购物
高三毕业生自我鉴定
2013/12/20 职场文书
《守株待兔》教学反思
2014/03/01 职场文书
欢迎标语大全
2014/06/21 职场文书
房屋租赁委托书范本
2014/10/04 职场文书
白酒代理协议书范本
2014/10/26 职场文书
工作年限证明模板
2014/11/01 职场文书
2014年学校卫生工作总结
2014/11/20 职场文书
python中的装饰器该如何使用
2021/06/18 Python
Python使用openpyxl批量处理数据
2021/06/23 Python
关于springboot配置druid数据源不生效问题(踩坑记)
2021/09/25 Java/Android