浅谈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 相关文章推荐
JavaScript可否多线程? 深入理解JavaScript定时机制
May 23 Javascript
js写出遮罩层登陆框和对联广告并自动跟随滚动条滚动
Apr 29 Javascript
浅析javascript中的事件代理
Nov 06 Javascript
js+css实现回到顶部按钮(back to top)
Mar 02 Javascript
关于网页中的无缝滚动的js代码
Jun 09 Javascript
Javascript6中字符串的四个新用法分享
Sep 11 Javascript
javascript数组定义的几种方法
Oct 06 Javascript
教你用Cordova打包Vue项目的方法
Oct 17 Javascript
es6新特性之 class 基本用法解析
May 05 Javascript
Vue EventBus自定义组件事件传递
Jun 25 Javascript
vue添加自定义右键菜单的完整实例
Dec 08 Vue.js
Vue 3自定义指令开发的相关总结
Jan 29 Vue.js
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
安健A254立体声随身听的分析与打磨
2021/03/02 无线电
PHP 时间转换Unix时间戳代码
2010/01/22 PHP
使用ThinkPHP自带的Http类下载远程图片到本地的实现代码
2011/08/02 PHP
解决FastCGI 进程超过了配置的活动超时时限的问题
2013/07/03 PHP
php单文件版在线代码编辑器
2015/03/12 PHP
JQury slideToggle闪烁问题及解决办法
2011/07/05 Javascript
漂亮的jquery提示效果(仿腾讯弹出层)
2013/02/05 Javascript
封装的jquery翻页滚动(示例代码)
2013/11/18 Javascript
js中的getAttribute方法使用示例
2014/08/01 Javascript
jquery中push()的用法(数组添加元素)
2014/11/25 Javascript
jQuery对JSON数据进行排序输出的方法
2015/06/24 Javascript
jquery使用ul模拟select实现表单美化的方法
2015/08/18 Javascript
jquery实现倒计时功能
2015/12/28 Javascript
jQuery实现手机自定义弹出输入框
2016/06/13 Javascript
详解JavaScript跨域总结与解决办法
2016/10/31 Javascript
jQuery实现表格与ckeckbox的全选与单选功能
2016/11/24 Javascript
[js高手之路]从原型链开始图解继承到组合继承的产生详解
2017/08/28 Javascript
微信小程序实现留言板(Storage)
2018/11/02 Javascript
vue使用video插件vue-video-player详解
2020/10/23 Javascript
jQuery实现移动端扭蛋机抽奖
2020/11/08 jQuery
[04:50]DOTA2亚洲邀请赛小组赛第四日 TOP10精彩集锦
2015/02/02 DOTA
[47:03]完美世界DOTA2联赛PWL S3 access vs LBZS 第一场 12.20
2020/12/23 DOTA
pycharm恢复默认设置或者是替换pycharm的解释器实例
2018/10/29 Python
Python Numpy数组扩展repeat和tile使用实例解析
2019/12/09 Python
在Anaconda3下使用清华镜像源安装TensorFlow(CPU版)
2020/04/19 Python
Clarins娇韵诗美国官网:法国天然护肤品牌
2016/09/26 全球购物
美国Curacao百货连锁店网站:iCuracao.com
2019/07/20 全球购物
体育纪念品、亲笔签名的体育收藏品:Steiner Sports
2020/07/31 全球购物
用JAVA SOCKET编程,读服务器几个字符,再写入本地显示
2012/11/25 面试题
管理部副部长岗位职责范文
2014/03/09 职场文书
三万活动总结
2014/04/28 职场文书
干部年终考核评语
2015/01/04 职场文书
违规违纪检讨书范文
2015/05/06 职场文书
Mysql中调试存储过程最简单的方法
2021/06/30 MySQL
golang生成并解析JSON
2022/04/14 Golang
mysql 子查询的使用
2022/04/28 MySQL