浅谈angular4 ng-content 中隐藏的内容
如果你尝试在 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,这正是我们之前预期的结果。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
- Django - - - -视图层之视图函数(views)
- fiddler mock ==> AutoResponder
- 基于Node.js开发跨平台窗口程序
- Django视图层之路由配置系统(urls)
- java String时间转为时间戳
- linux 简易启动脚本
- 2017年我国大数据产业发展五大新突破
- Django---MTV模型、基本命令、简单配置
- Vue.js 入门
- 解读python中SocketServer源码
- java学习:Hibernate学习-用oracle sequence序列生成ID的配置示例
- Wikibon年度预测:2018年人工智能的八个演变趋势
- Google宣布TensorFlow Lite 可支持 Core ML!
- 开源软件十位贡献者,PHP之父只能排第十,第一名当之无愧!
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- LeetCode刷题DAY 37:组合总合
- 利用scipy计算定积分
- 关于设计模式的那些事(一)
- 你在小程序中怎么计算两个经纬度的距离?
- 权限-Linux每日一练(5)
- Go 每日一库之 commonregex
- Python编程 基础练习(三)
- Python 图像数组变换及手绘效果实现
- 爬取TIOBE编程语言热度数据 + Pyecharts绘制时间轮播图
- Python 简单实现贪吃蛇小游戏
- Python数据可视化 词云图 绘制词云的方法总结
- Python 利用OpenCV给照片换底色
- Python爬虫实战 批量下载高清美女图片
- Python matplotlib数据可视化 绘制柱形图、堆叠图、折线图、饼图和环图
- Python Matplotlib数据可视化 绘制箱形图、散点图和直方图