浅谈angular4 ng-content 中隐藏的内容


Posted in Javascript onAugust 18, 2017

如果你尝试在 Angular 中编写可重复使用的组件,则可能会接触到内容投射的概念。然后你发现了 <ng-content> ,并找到了一些关于它的文章,进而实现了所需的功能。

接下来我们来通过一个简单的示例,一步步介绍 <ng-content> 所涉及的内容。

Simple example

在本文中我们使用一个示例,来演示不同的方式实现内容投影。由于许多问题与Angular 中的组件生命周期相关,因此我们的主要组件将显示一个计数器,用于展示它已被实例化的次数:

import { Component } from '@angular/core';

let instances = 0;

@Component({
 selector: 'counter',
 template: '<h1>{{this.id}}</h1>'
})
class Counter {
 id: number;
 
 constructor() {
  this.id = ++instances;
 }
}

上面示例中我们定义了 Counter 组件,组件类中的 id 属性用于显示本组件被实例化的次数。接着我们继续定义一个 Wrapper 组件:

import { Component } from '@angular/core';

@Component({
 selector: 'wrapper',
 template: `
  <div class="box">
   <ng-content></ng-content>
  </div>
 `
})
class Wrapper {}

现在我们来验证一下效果:

<wrapper>
 <counter></counter>
 <counter></counter>
 <counter></counter>
</wrapper>

Targeted projection

有时你希望将包装器的不同子项投影到模板的不同部分。为了处理这个问题, <ng-content> 支持一个 select 属性,可以让你在特定的地方投射具体的内容。该属性支持 CSS 选择器(my-element,.my-class,[my-attribute],...)来匹配你想要的内容。如果 ng-content 上没有设置 select 属性,它将接收全部内容,或接收不匹配任何其他 ng-content 元素的内容。长话短说:

import { Component } from '@angular/core';

@Component({
 selector: 'wrapper',
 template: `
 <div class="box red">
  <ng-content></ng-content>
 </div>
 <div class="box blue">
  <ng-content select="counter"></ng-content>
 </div>
 `,
 styles: [`
  .red {background: red;}
  .blue {background: blue;}
 `]
})
export class Wrapper { }

上面示例中,我们引入了 select 属性,来选择投射的内容:

<wrapper>
 <span>This is not a counter</span>
 <counter></counter>
</wrapper>

上述代码成功运行后,counter 组件被正确投影到第二个蓝色框中,而 span 元素最终会在全部红色框中。请注意,目标 ng-content 会优先于 catch-all,即使它在模板中的位置靠后。

ngProjectAs

有时你的内部组件会被隐藏在另一个更大的组件中。有时你只需要将其包装在额外的容器中即可应用 ngIf ngSwitch。无论什么原因,通常情况下,你的内部组件不是包装器的直接子节点。为了演示上述情况,我们将 Counter 组件包装在一个 <ng-container> 中,看看我们的目标投影会发生什么:

<wrapper>
 <ng-container>
  <counter></counter>
 </ng-container>
</wrapper>

现在我们的 couter 组件会被投影到第一个红色框中。因为 ng-container 容器不再匹配 select="counter"。为了解决这个问题,我们必须使用 ngProjectAs 属性,它可以应用于任何元素上。具体如下:

<wrapper>
 <ng-container ngProjectAs="counter">
  <counter></counter>
 </ng-container>
</wrapper>

通过设置 ngProjectAs 属性,终于让我们的 counter 组件重回蓝色框的怀抱了。

Time to poke and prod

我们从一个简单的实验开始:将两个 <ng-content> 块放在我们的模板中,没有选择器。会出现什么情况?

页面中会显示一个或两个框,如果我们包含两个框,它们的内容是显示 1 和 1 或 1 和 2?

<div class="box red">
  <ng-content></ng-content>
</div>
<div class="box blue">
  <ng-content></ng-content>
</div>

答案是我们在最后一个 <ng-content> 中得到一个计数器,另一个是空的!在我们尝试解释为什么之前,让我们再来验证一个问题,即在 ng-content 指令的外层容器中添加 ngIf 指令:

import { Component } from '@angular/core';

@Component({
 selector: 'wrapper',
 template: `
  <button (click)="show = !show">
   {{ show ? 'Hide' : 'Show' }}
  </button>
  <div class="box" *ngIf="show">
   <ng-content></ng-content>
  </div>
 `
})
class Wrapper {
 show = true;
}

乍一看,似乎正常运行。但是如果你通过按钮进行切换操作,你会注意到计数器的值不会增加。这意味着我们的计数器组件只被实例化了一次 - 从未被销毁和重新创建。难道这是 ngIf 指令产生的问题,让我们测试一下 ngFor 指令,看看是否有同样的问题:

import { Component } from '@angular/core';

@Component({
 selector: 'wrapper',
 template: `
  <div class="box" *ngFor="let item of items">
   <ng-content></ng-content>
  </div>
 `
})
class Wrapper {
 items = [0, 0, 0];
}

以上代码运行后与我们使用多个 <ng-content> 的效果是一样的,只会显示一个计数器!为什么不按照我们的预期运行?

The explanation

<ng-content> 不会 "产生" 内容,它只是投影现有的内容。你可以认为它等价于 node.appendChild(el) 或 jQuery 中的 $(node).append(el) 方法:使用这些方法,节点不被克隆,它被简单地移动到它的新位置。因此,投影内容的生命周期将被绑定到它被声明的地方,而不是显示在地方。

这种行为有两个原因:期望一致性和性能。什么 "期望的一致性" 意味着作为开发人员,可以基于应用程序的代码,猜测其行为。假设我写了以下代码:

<div class="my-wrapper">
 <counter></counter>
</div>

很显然计数器将被实例化一次,但现在假如我们使用第三方库的组件:

<third-party-wrapper>
 <counter></counter>
</third-party-wrapper>

如果第三方库能够控制 counter 组件的生命周期,我将无法知道它被实例化了多少次。其中唯一方法就是查看第三方库的代码,了解它们的内部处理逻辑。将组件的生命周期被绑定到我们的应用程序组件而不是包装器的意义是,开发者可以掌控计数器只被实例化一次,而不用了解第三方库的内部代码。

性能的原因更为重要。因为 ng-content 只是移动元素,所以可以在编译时完成,而不是在运行时,这大大减少了实际应用程序的工作量。

The solution

为了让包装器能够控制其子元素的实例化,我们可以通过两种方式完成:在我们的内容周围使用 <ng-template> 元素,或者使用带有 "*" 语法的结构指令。为简单起见,我们将在示例中使用 <ng-template> 语法,我们的新应用程序如下所示:

<wrapper>
 <ng-template>
  <counter></counter>
 </ng-template>
</wrapper>

包装器不再使用 <ng-content>,因为它接收到一个模板。我们需要使用 @ContentChild 访问模板,并使用ngTemplateOutlet 来显示它:

@Component({
 selector: 'wrapper',
 template: `
  <button (click)="show = !show">
   {{ show ? 'Hide' : 'Show' }}
  </button>
  <div class="box" *ngIf="show">
   <ng-container [ngTemplateOutlet]="template"></ng-container>
  </div>
 `
})
class Wrapper {
 show = true;
 @ContentChild(TemplateRef) template: TemplateRef;
}

现在我们的 counter 组件,每当我们隐藏并重新显示时都正确递增!让我们再验证一下 *ngFor 指令:

@Component({
 selector: 'wrapper',
 template: `
  <div class="box" *ngFor="let item of items">
   <ng-container [ngTemplateOutlet]="template"></ng-container>
  </div>
 `
})
class Wrapper {
 items = [0, 0, 0];
 @ContentChild(TemplateRef) template: TemplateRef;
}

上面代码成功运行后,每个盒子中有一个计数器,显示 1,2 和 3,这正是我们之前预期的结果。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
表单提交验证类
Jul 14 Javascript
jquery简单实现鼠标经过导航条改变背景图
Dec 17 Javascript
JQuery 控制内容长度超出规定长度显示省略号
May 23 Javascript
jQuery使用hide方法隐藏指定元素class样式用法实例
Mar 30 Javascript
js阻止冒泡和默认事件(默认行为)详解
Oct 20 Javascript
jQuery 判断元素整理汇总
Feb 28 Javascript
ES6使用let命令更简单的实现块级作用域实例分析
Mar 31 Javascript
如何选择jQuery版本 1.x? 2.x? 3.x?
Apr 01 jQuery
JavaScript严格模式下关于this的几种指向详解
Jul 12 Javascript
通过一个简单的例子学会vuex与模块化
Nov 22 Javascript
vue中watch和computed为什么能监听到数据的改变以及不同之处
Dec 27 Javascript
vue3.0搭配.net core实现文件上传组件
Oct 29 Javascript
Node.JS更改Windows注册表Regedit的方法小结
Aug 18 #Javascript
浅谈Angular2 ng-content 指令在组件中嵌入内容
Aug 18 #Javascript
Vue中引入样式文件的方法
Aug 18 #Javascript
jQuery+HTML5实现WebGL高性能烟花绽放动画效果【附demo源码下载】
Aug 18 #jQuery
JS实现基于Sketch.js模拟成群游动的蝌蚪运动动画效果【附demo源码下载】
Aug 18 #Javascript
select自定义小三角样式代码(实用总结)
Aug 18 #Javascript
js使用highlight.js高亮你的代码
Aug 18 #Javascript
You might like
用php将任何格式视频转为flv的代码
2009/09/03 PHP
php imagecreatetruecolor 创建高清和透明图片代码小结
2010/05/15 PHP
php 中文和编码判断代码
2010/05/16 PHP
PHP isset()与empty()的使用区别详解
2010/08/29 PHP
PHP的一个基础知识 表单提交
2011/07/04 PHP
phpExcel中文帮助手册之常用功能指南
2014/08/18 PHP
在Thinkphp中使用ajax实现无刷新分页的方法
2016/10/25 PHP
PHP基于自定义函数生成笛卡尔积的方法示例
2017/09/30 PHP
Laravel等框架模型关联的可用性浅析
2019/12/15 PHP
关于Javascript模块化和命名空间管理的问题说明
2010/12/06 Javascript
jQuery固定浮动侧边栏实现思路及代码
2014/09/28 Javascript
JavaScript操作HTML DOM节点的基础教程
2016/03/11 Javascript
Bootstrap学习笔记之css样式设计(1)
2016/06/07 Javascript
javascript简单实现等比例缩小图片的方法
2016/07/27 Javascript
推荐10款扩展Web表单的JS插件
2017/12/25 Javascript
jQuery插件jsonview展示json数据
2018/05/26 jQuery
Angular-UI Bootstrap组件实现警报功能
2018/07/16 Javascript
vue-cli 目录结构详细讲解总结
2019/01/15 Javascript
vue引入静态js文件的方法
2020/06/20 Javascript
JavaScript的垃圾回收机制与内存管理
2020/08/06 Javascript
vue 动态给每个页面添加title、关键词和描述的方法
2020/08/28 Javascript
python网络爬虫采集联想词示例
2014/02/11 Python
详解Python爬虫的基本写法
2016/01/08 Python
Python中时间datetime的处理与转换用法总结
2019/02/18 Python
浅谈python之自动化运维(Paramiko)
2020/01/31 Python
ansible-playbook实现自动部署KVM及安装python3的详细教程
2020/05/11 Python
python中doctest库实例用法
2020/12/31 Python
HTML5 移动页面自适应手机屏幕四类方法总结
2017/08/17 HTML / CSS
美国顶级户外凉鞋品牌:Chacos
2017/03/27 全球购物
巴西Bo.Bô官方在线商店:经营奢侈品时尚业务
2020/03/16 全球购物
戴森比利时官方网站:Dyson BE
2020/10/03 全球购物
任课老师推荐信范文
2013/11/24 职场文书
实习自我鉴定
2013/12/15 职场文书
工作中个人的自我评价
2013/12/31 职场文书
企业安全生产演讲稿
2014/05/09 职场文书
校园运动会广播稿
2015/08/19 职场文书