如果你尝试在 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,这正是我们之前预期的结果。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# angular4
# ng
# content隐藏内容
# content
# 浅谈Angular2 ng-content 指令在组件中嵌入内容
# 如何用DevUI搭建自己的Angular组件库
# 详解Angular数据绑定及其实现方式
# 详解React Angular Vue三大前端技术
# Angular性能优化之第三方组件和懒加载技术
# Angular框架详解之视图抽象定义
# AngularJS 中括号的作用详解
# 详解Angular组件之投影
# 让我们
# 第三方
# 应用程序
# 如果你
# 框中
# 将被
# 绑定
# 到第
# 装在
# 而不是
# 这是
# 是在
# 选择器
# 让你
# 你可以
# 多个
# 在我们的
# 你会
# 将在
# 两种
相关文章:
建站主机数据库如何配置才能提升网站性能?
如何快速搭建高效香港服务器网站?
东莞专业制作网站的公司,东莞大学生网的网址是什么?
制作电商网页,电商供应链怎么做?
建站之星如何修改网站生成路径?
如何快速选择适合个人网站的云服务器配置?
西安专业网站制作公司有哪些,陕西省建行官方网站?
建站之星代理商如何保障技术支持与售后服务?
建站主机系统SEO优化与智能配置核心关键词操作指南
教程网站设计制作软件,怎么创建自己的一个网站?
如何在阿里云ECS服务器部署织梦CMS网站?
建站与域名管理如何高效结合?
图册素材网站设计制作软件,图册的导出方式有几种?
如何用西部建站助手快速创建专业网站?
如何通过VPS建站实现广告与增值服务盈利?
如何在Windows 2008云服务器安全搭建网站?
网站制作新手教程,新手建设一个网站需要注意些什么?
建站之星安装提示数据库无法连接如何解决?
如何设计高效校园网站?
如何注册花生壳免费域名并搭建个人网站?
如何在万网ECS上快速搭建专属网站?
免费网站制作appp,免费制作app哪个平台好?
高端建站三要素:定制模板、企业官网与响应式设计优化
微信小程序 五星评分(包括半颗星评分)实例代码
单页制作网站有哪些,朋友给我发了一个单页网站,我应该怎么修改才能把他变成自己的呢,请求高手指点迷津?
详解免费开源的.NET多类型文件解压缩组件SharpZipLib(.NET组件介绍之七)
建站之星如何助力网站排名飙升?揭秘高效技巧
如何基于PHP生成高效IDC网络公司建站源码?
网站制作需要会哪些技术,建立一个网站要花费多少?
如何在腾讯云服务器快速搭建个人网站?
小视频制作网站有哪些,有什么看国内小视频的网站,求推荐?
建站之星伪静态规则如何正确配置?
潮流网站制作头像软件下载,适合母子的网名有哪些?
成都网站制作公司哪家好,四川省职工服务网是做什么用?
宝塔面板创建网站无法访问?如何快速排查修复?
如何通过虚拟主机快速搭建个人网站?
昆明网站制作哪家好,昆明公租房申请网上登录入口?
建站之星安装需要哪些步骤及注意事项?
香港服务器网站推广:SEO优化与外贸独立站搭建策略
小型网站建站如何选择虚拟主机?
建站之星安装步骤有哪些常见问题?
厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?
如何挑选最适合建站的高性能VPS主机?
个人摄影网站制作流程,摄影爱好者都去什么网站?
北京营销型网站制作公司,可以用python做一个营销推广网站吗?
建站主机是什么?如何选择适合的建站主机?
专业网站建设制作报价,网页设计制作要考什么证?
如何规划企业建站流程的关键步骤?
官网自助建站系统:SEO优化+多语言支持,快速搭建专业网站
C#怎么创建控制台应用 C# Console App项目创建方法
*请认真填写需求信息,我们会在24小时内与您取得联系。