Angular开发实践(八): 使用ng-content进行组件内容投射
在Angular中,组件属于特殊的指令,它的特殊之处在于它有自己的模板(html)和样式(css)。因此使用组件可以使我们的代码具有强解耦、可复用、易扩展等特性。通常的组件定义如下:
demo.component.ts:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'demo-component',
templateUrl: './demo.component.html',
styleUrls: ['./demo.component.scss']
})
export class DemoComponent implements OnInit {
constructor() {
}
ngOnInit() {
}
}
demo.component.html:
<div class="demo">
<h2>
demo-component - 我是一个简单的组件
</h2>
</div>
demo.component.scss:
.demo {
padding: 10px;
border: 2px solid red;
h2 {
margin: 0;
color: #262626;
}
}
此时我们引用该组件,就会呈现该组件解析之后的内容:
<demo-component></demo-component>
假设现在有这样的需求,这个组件能够接受外部投射进来的内容,也就是说组件最终呈现的内容不仅仅是本身定义的那些,那该怎么做呢?这时就要请出本文的主角 ng-content
。
简单投射
我们先从最简单开始,在 demo.component.html 中添加 ,修改后的 demo.component.html 和 demo.component.scss 如下:
demo.component.html:
<div class="demo">
<h2>
demo-component - 可嵌入外部内容的组件
</h2>
<div class="content">
<ng-content></ng-content>
</div>
</div>
demo.component.scss:
.demo {
padding: 10px;
border: 2px solid red;
h2 {
margin: 0;
color: #262626;
}
.content {
padding: 10px;
margin-top: 10px;
line-height: 20px;
color: #FFFFFF;
background-color: #de7d28;
}
}
为了效果展示特意将 所在的容器背景色定义为橙色。
这时我们在引用该组件时可以从外部投射内容,外部内容将在橙色区域显示:
<demo-component>
我是外部嵌入的内容
</demo-component>
针对性投射
如果同时存在几个 ,那外部内容将如何进行投射呢?
我们先看个示例,为了区别,我再新增一个蓝色区域的 ,修改后的 demo.component.html 和 demo.component.scss 如下:
demo.component.html:
<div class="demo">
<h2>
demo-component - 可嵌入外部内容的组件
</h2>
<div class="content">
<ng-content></ng-content>
</div>
<div class="content blue">
<ng-content></ng-content>
</div>
</div>
demo.component.scss:
.demo {
padding: 10px;
border: 2px solid red;
h2 {
margin: 0;
color: #262626;
}
.content {
padding: 10px;
margin-top: 10px;
line-height: 20px;
color: #FFFFFF;
background-color: #de7d28;
&.blue {
background-color: blue;
}
}
}
引用该组件:
<demo-component>
我是外部嵌入的内容
</demo-component>
此时,我们将看到外部内容投射到了蓝色区域:
当然,如果你将橙色区域代码放在蓝色区域代码的后面,那么外部内容就会投射到橙色区域:
所以从上面的示例我们可以看出,如果同时存在简单的 ,那么外部内容将投射在组件模板最后的那个 中。
那么知道这个问题,我们可能会想,能不能将外部内容有针对性的投射相应的 中呢?答案显然是可以的。
为了处理这个问题, 支持一个 select
属性,可以让你在特定的地方投射具体的内容。该属性支持 CSS 选择器(标签选择器、类选择器、属性选择器、...)来匹配你想要的内容。如果 ng-content 上没有设置 select
属性,它将接收全部内容,或接收不匹配任何其他 ng-content 元素的内容。
直接看例子,修改后的 demo.component.html 和 demo.component.scss 如下:
demo.component.html:
<div class="demo">
<h2>
demo-component - 可嵌入外部内容的组件
</h2>
<div class="content">
<ng-content></ng-content>
</div>
<div class="content blue">
<ng-content select="header"></ng-content>
</div>
<div class="content red">
<ng-content select=".demo2"></ng-content>
</div>
<div class="content green">
<ng-content select="[name=demo3]"></ng-content>
</div>
</div>
demo.component.scss:
.demo {
padding: 10px;
border: 2px solid red;
h2 {
margin: 0;
color: #262626;
}
.content {
padding: 10px;
margin-top: 10px;
line-height: 20px;
color: #FFFFFF;
background-color: #de7d28;
&.blue {
background-color: blue;
}
&.red {
background-color: red;
}
&.green {
background-color: green;
}
}
}
从上面代码可以看到,蓝色区域将接收 标签 header
那部分内容,红色区域将接收 class为"demo2"的div
的那部分内容,绿色区域将接收 属性name为"demo3"的div
的那部分内容,橙色区域将接收其余的外部内容(开始,我是外部嵌入的内容,结束
)。
引用该组件:
<demo-component>
开始,我是外部嵌入的内容,
<header>
我是外部嵌入的内容,我在header中
</header>
<div class="demo2">
我是外部嵌入的内容,我所在div的class为"demo2"
</div>
<div name="demo3">
我是外部嵌入的内容demo,我所在div的属性name为"demo3"
</div>
结束
</demo-component>
此时,我们将看到外部内容投射到了指定的 中。
扩展知识
ngProjectAs
现在我们知道通过 ng-content 的 select
属性可以指定外部内容投射到指定的 中。
而要能正确的根据 select
属性投射内容,有个限制就是 - 不管是 标签 header
、class为"demo2"的div
还是 属性name为"demo3"的div
,这几个标签都是作为 组件标签 的直接子节点
。
那如果不是作为直接子节点,会是什么情况呢?我们简单修改下引用 demo-component 组件的代码,将 标签header
放在一个div中,修改如下:
<demo-component>
开始,我是外部嵌入的内容,
<div>
<header>
我是外部嵌入的内容,我在header中
</header>
</div>
<div class="demo2">
我是外部嵌入的内容,我所在div的class为"demo2"
</div>
<div name="demo3">
我是外部嵌入的内容demo,我所在div的属性name为"demo3"
</div>
结束
</demo-component>
此时,我们看到 标签 header
那部分内容不再投射到蓝色区域中了,而是投射到橙色区域中了。原因就是 <ng-content select="header"></ng-content>
无法匹配到之前的 标签 header
,故而将这部分内容投射到了橙色区域的 <ng-content></ng-content>
中了。
为了解决这个问题,我们必须使用 ngProjectAs
属性,它可以应用于任何元素上。具体如下:
<demo-component>
开始,我是外部嵌入的内容,
<div ngProjectAs="header">
<header>
我是外部嵌入的内容,我在header中
</header>
</div>
<div class="demo2">
我是外部嵌入的内容,我所在div的class为"demo2"
</div>
<div name="demo3">
我是外部嵌入的内容demo,我所在div的属性name为"demo3"
</div>
结束
</demo-component>
通过设置 ngProjectAs
属性,让 标签header
所在的 div 指向了 select="header"
,此时 标签 header
那部分内容有投射到蓝色区域了:
<ng-content>
不“产生”内容
做个试验
做个试验,先定义一个 demo-child-component 组件:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'demo-child-component',
template: '<h3>我是demo-child-component组件</h3>'
})
export class DemoChildComponent implements OnInit {
constructor() {
}
ngOnInit() {
console.log('demo-child-component初始化完成!');
}
}
demo-component 组件修改为:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'demo-component',
template: `
<button (click)="show = !show">
{{ show ? 'Hide' : 'Show' }}
</button>
<div class="content" *ngIf="show">
<ng-content></ng-content>
</div>
`
})
export class DemoComponent implements OnInit {
show = true;
constructor() {
}
ngOnInit() {
}
}
然后在 demo-component 中 投射 demo-child-component:
<demo-component>
<demo-child-component></demo-child-component>
</demo-component>
此时,在控制台我们看到打印出 demo-child-component初始化完成!
这些文字。但是当我们点击按钮进行切换操作时,demo-child-component初始化完成!
就不再打印了,这意味着我们的 demo-child-component 组件只被实例化了一次 - 从未被销毁和重新创建。
为什么会出现这样的情况呢?
出现原因
<ng-content>
不会 "产生" 内容,它只是投影现有的内容。你可以认为它等价于 node.appendChild(el)
或 jQuery 中的 $(node).append(el)
方法:使用这些方法,节点不被克隆,它被简单地移动到它的新位置。因此,投影内容的生命周期将被绑定到它被声明的地方,而不是显示在地方。
这也从原理解释了前面那个问题:如果同时存在几个 ,那外部内容将如何进行投射呢?
这种行为有两个原因:期望一致性和性能。什么 "期望的一致性" 意味着作为开发人员,可以基于应用程序的代码,猜测其行为。假设我写了以下代码:
<demo-component>
<demo-child-component></demo-child-component>
</demo-component>
很显然 demo-child-component 组件将被实例化一次,但现在假如我们使用第三方库的组件:
<third-party-wrapper>
<demo-child-component></demo-child-component>
</third-party-wrapper>
如果第三方库能够控制 demo-child-component 组件的生命周期,我将无法知道它被实例化了多少次。其中唯一方法就是查看第三方库的代码,了解它们的内部处理逻辑。将组件的生命周期被绑定到我们的应用程序组件而不是包装器的意义是,开发者可以掌控计数器只被实例化一次,而不用了解第三方库的内部代码。
性能的原因 更为重要。因为 ng-content 只是移动元素,所以可以在编译时完成,而不是在运行时,这大大减少了实际应用程序的工作量。
解决方法
为了让组件能够控制投射进来的子组件的实例化,我们可以通过两种方式完成:在我们的内容周围使用 <ng-template>
元素及 ngTemplateOutlet,或者使用带有 "*" 语法的结构指令。为简单起见,我们将在示例中使用 <ng-template>
语法。
demo-component 组件修改为:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'demo-component',
template: `
<button (click)="show = !show">
{{ show ? 'Hide' : 'Show' }}
</button>
<div class="content" *ngIf="show">
<ng-container [ngTemplateOutlet]="template"></ng-container>
</div>
`
})
export class DemoComponent implements OnInit {
@ContentChild(TemplateRef) template: TemplateRef;
show = true;
constructor() {
}
ngOnInit() {
}
}
然后我们将 demo-child-component 包含在 ng-template
中:
<demo-component>
<ng-template>
<demo-child-component></demo-child-component>
</ng-template>
</demo-component>
此时,我们在点击按钮进行切换操作时,控制台都会打印出 demo-child-component初始化完成!
这些文字。
参考资源
Angular开发实践(八): 使用ng-content进行组件内容投射的更多相关文章
- Angular开发实践(一):环境准备及框架搭建
引言 在工作中引入Angular框架将近一年了,在这一年中不断的踩坑和填坑,当然也学习和积累了很多的知识,包括MVVM框架.前后端分离.前端工程化.SPA优化等等.因此想通过Angular开发实践这系 ...
- Angular开发实践(七): 跨平台操作DOM及渲染器Renderer2
在<Angular开发实践(六):服务端渲染>这篇文章的最后,我们也提到了在服务端渲染中需要牢记的几件事件,其中就包括不要使用window. document. navigator等浏览器 ...
- [转]Angular开发(十八)-路由的基本认识
angular router https://angular.io/guide/router 本文转自:https://blog.csdn.net/kuangshp128/article/detail ...
- Angular开发实践(三):剖析Angular Component
Web Component 在介绍Angular Component之前,我们先简单了解下W3C Web Components 定义 W3C为统一组件化标准方式,提出Web Component的标准. ...
- Angular开发实践(四):组件之间的交互
在Angular应用开发中,组件可以说是随处可见的.本篇文章将介绍几种常见的组件通讯场景,也就是让两个或多个组件之间交互的方法. 根据数据的传递方向,分为父组件向子组件传递.子组件向父组件传递及通过服 ...
- Angular开发实践(六):服务端渲染
Angular Universal Angular在服务端渲染方面提供一套前后端同构解决方案,它就是 Angular Universal(统一平台),一项在服务端运行 Angular 应用的技术. 标 ...
- Angular开发实践(五):深入解析变化监测
什么是变化监测 在使用 Angular 进行开发中,我们常用到 Angular 中的绑定--模型到视图的输入绑定.视图到模型的输出绑定以及视图与模型的双向绑定.而这些绑定的值之所以能在视图与模型之间保 ...
- ionic,Angular 开发实践
1.实践参考 http://www.jianshu.com/p/ea0dcf1d31c9 原文思路搭建 2. 环境搭建步骤 : a. 安装node b.安装 cordova sudo n ...
- Angular开发实践(二):HRM运行机制
引言 在angular-start项目中启用了模块热替换(HMR - Hot Module Replacement)功能,关于如何在angular-cli启用HRM,请查看HRM配置 那HMR是个什么 ...
随机推荐
- 通过Navicat连接MySQL数据库
步骤一.从Navicat官网下载Navicat11版本安装包安装 下载连接:http://www.formysql.com/xiazai_mysql.html 步骤二.下载补丁破解程序PatchNav ...
- ubuntu16.04 安装常见问题解决方案------输入法黑框
我的系统是 lubuntu 16.04 刚安装输入法候选字的地方全是黑框,然后百度查到了 compton 和 xcompmgr 这两个说是窗口微调 透明 ,这两个方法对我的系统不管用 .各位如果遇到黑 ...
- angularjs 缓存详解
一.什么是缓存 一个缓存就是一个组件,它可以透明地存储数据,以便未来可以更快地服务于请求. 缓存能够服务的请求越多,整体系统性能就提升得越多. 二.Angular 中的缓存 2.1 $cacheFac ...
- parted分区及挂载实战操作大全
个人原创博客,转载请注明,否则要负法律责任 2017-09-29-14:46:25[root@localhost ~]# df -hFilesystem Size Used Avail Use% Mo ...
- LNMP详解
目录 Nginx配置 1 PHP解析 1 Mysql操作 3 服务安装 3 连接测试 3 数据配置 3 Blogs建立 4 LNMP 环境 Mysql:1 ...
- 基于Three.js的360度全景--photo-sphere-viewer--简介
这个是基于three.js的全景插件 photo-sphere-viewer.js ---------------------------------------- 1.能添加热点: 2.能调用陀 ...
- CentOS7.2下Nginx的使用
Nginx的启动 指定配置文件的方式启动 nginx -c /etc/nginx/nginx.conf 对于yum安装的nginx,使用systemctl命令启动 systemctl start ng ...
- java基础学习系列三
产生随机数 例如 [a,b] Math.random*(b-a+1)+a 公式推算 [3,55]-----[0,52]+3 *53+3
- 分布式事务的典型处理方式:2PC、TCC、异步确保和最大努力型
1. 柔性事务和刚性事务 柔性事务满足BASE理论(基本可用,最终一致)刚性事务满足ACID理论 本文主要围绕分布式事务当中的柔性事务的处理方式进行讨论. 柔性事务分为 两阶段型 补偿型 异步确保型 ...
- c++ --> sizeof()使用小结
sizeof()使用小结 特性0:sizeof是运算符,不是函数 sizeof最基本特性,后面的很多特性都是受到这个特性的影响,正因为sizeof不是函数,因此不把它所要求得长度的对象叫做参数,习惯上 ...