浅谈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实现的分页插件pagenav
Aug 28 Javascript
javascript实现倒计时并弹窗提示特效
Jun 05 Javascript
jQuery实现图片轮播特效代码分享
Sep 15 Javascript
灵活的理解JavaScript中的this指向
Feb 25 Javascript
关于session和cookie的简单理解
Jun 08 Javascript
angularJS 指令封装回到顶部示例详解
Jan 22 Javascript
关于Bootstrap按钮组件消除黄框的方法
May 19 Javascript
详解Vue单元测试Karma+Mocha学习笔记
Jan 31 Javascript
vue2.0在没有dev-server.js下的本地数据配置方法
Feb 23 Javascript
Vue 实现展开折叠效果的示例代码
Aug 27 Javascript
JS实现json数组排序操作实例分析
Oct 28 Javascript
微信小程序实现选择地址省市区三级联动
Jun 21 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
NOD32 v2.70.32 简体中文封装版 提供下载了
2007/02/27 PHP
PHP查看SSL证书信息的方法
2016/09/22 PHP
php使用curl下载指定大小的文件实例代码
2017/09/30 PHP
Smarty模板变量与调节器实例详解
2019/07/20 PHP
JQUERY对单选框(radio)操作的小例子
2013/04/25 Javascript
HTML长文本截取含有HTML代码同样适用的两种方法
2013/07/31 Javascript
jquery中$(#form :input)与$(#form input)的区别
2014/08/18 Javascript
jQuery中val()方法用法实例
2014/12/25 Javascript
浅谈被jQuery抛弃的函数及替代函数
2015/05/03 Javascript
js获取客户端操作系统类型的方法【测试可用】
2016/05/27 Javascript
JS实现的DIV块来回滚动效果示例
2017/02/07 Javascript
微信小程序中input标签详解及简单实例
2017/05/18 Javascript
vue组件之Alert的实现代码
2017/10/17 Javascript
浅谈vue中慎用style的scoped属性
2017/11/28 Javascript
JavaScript定义及输出螺旋矩阵的方法详解
2017/12/01 Javascript
微信小程序实现带缩略图轮播效果
2018/11/04 Javascript
详解在微信小程序的JS脚本中使用Promise来优化函数处理
2019/03/06 Javascript
JavaScript页面加载事件实例讲解
2019/09/01 Javascript
layui中的switch开关实现方法
2019/09/03 Javascript
Python pickle模块用法实例分析
2015/05/27 Python
Python信息抽取之乱码解决办法
2017/06/29 Python
python实现百万答题自动百度搜索答案
2018/01/16 Python
Python 获得命令行参数的方法(推荐)
2018/01/24 Python
详解利用django中间件django.middleware.csrf.CsrfViewMiddleware防止csrf攻击
2018/10/09 Python
python读csv文件时指定行为表头或无表头的方法
2019/06/26 Python
Python 硬币兑换问题
2019/07/29 Python
如何使用python代码操作git代码
2020/02/29 Python
介绍下Java中==和equals的区别
2013/09/01 面试题
自我鉴定书面格式
2014/01/13 职场文书
中专生自我鉴定范文
2014/02/02 职场文书
《晏子使楚》教学反思
2014/02/08 职场文书
港澳通行证委托书怎么写
2014/08/02 职场文书
2015年收银工作总结范文
2015/04/01 职场文书
2015年社区科普工作总结
2015/05/13 职场文书
2016年度师德标兵先进事迹材料
2016/02/26 职场文书
HTML5之高度塌陷问题的解决
2022/06/01 HTML / CSS