前面的文章我们已经介绍过了 Angular 2 的 ViewChild & ViewChildren 属性装饰器,现在我们就来介绍一下它们的兄弟 ContentChild 和 ContentChildren 属性装饰器。我想通过一个简单的需求,来引入我们今天的主题。具体需求如下:
熟悉 Angular 1.x 的用户,应该都知道 ng-transclude
指令,通过该指令我们可以非常容易实现上述的功能。而在 Angular 2 中引入新的 ng-content
指令,我们马上动手试一下。
greet.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'exe-greet',
template: `
Greet Component
`,
styles: [` .border { border: 2px solid #eee; } `]
})
export class GreetComponent { }
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
Welcome to Angular World
Hello Semlinker
`,
})
export class AppComponent { }
以上代码运行后,浏览器的输出结果:
是不是感觉太简单了,那我们来升级一下需求,即我们的 GreetComponent 组件要支持用户自定义卡片风格的问候内容,具体如下图所示:
这个功能也很简单啊,我们马上看一下代码:
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
Welcome to Angular World
Card Header
Card Body
Card Footer
`,
})
export class AppComponent { }
以上代码运行后,浏览器的输出结果:
功能是实现了,但有没有什么问题 ?假设在另一个页面,也需要使用 GreetComponent 组件 ,那么还需要再次设置预设的样式。我们能不能把卡片风格的各个部分默认样式定义在 GreetComponent 组件中,使用组件时我们只需关心自定内容区域的样式,答案是可以的,调整后的代码如下:
import { Component } from '@angular/core';
@Component({
selector: 'exe-greet',
template: `
Greet Component
`,
styles: [` .border { border: 2px solid #eee; } `]
})
export class GreetComponent{ }
GreetComponent 组件已经调整好了,现在剩下的问题就是如何从父级组件动态的抽取各个部分的内容。幸运的是,ng-content
指令支持 select
属性,它允许我们设定抽取的内容,更强大的是它支持我们常用的选择器类型,如标签选择器、类选择器、ID选择器、属性选择器等。
greet.component.ts - template
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
Welcome to Angular World
Card Header
Card Body
`,
})
export class AppComponent { }
以上代码运行后,在浏览上我们可以看到预期的结果,接下来该我们的主角 - ContentChild 出场了。
ContentChild
在正式介绍 ContentChild 属性装饰器前,我们要先了解一下 Content Projection (内容投影)。
以前面的例子为例,我们在 AppComponent
组件中定义的内容,通过 ng-content
指令提供的选择器,成功投射到 GreetComponent 组件中。了解完 Content Projection,接下来我们来介绍 ContentChild。
ContentChild 是属性装饰器,用来从通过 Content Projection 方式设置的视图中获取匹配的元素。
@ContentChild 示例
child.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'exe-child',
template: `
Child Component
`
})
export class ChildComponent {
name: string = 'child-component';
}
parent.component.ts
import { Component, ContentChild, AfterContentInit } from '@angular/core';
import { ChildComponent } from './child.component';
@Component({
selector: 'exe-parent',
template: `
Parent Component
`
})
export class ParentComponent implements AfterContentInit {
@ContentChild(ChildComponent)
childCmp: ChildComponent;
ngAfterContentInit() {
console.dir(this.childCmp);
}
}
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
Welcome to Angular World
`,
})
export class AppComponent { }
以上代码运行后,控制台的输出结果:
ContentChildren
ContentChildren 属性装饰器用来从通过 Content Projection 方式设置的视图中获取匹配的多个元素,返回的结果是一个 QueryList 集合。
@ContentChildren 示例
parent.component.ts
import { Component, ContentChildren, QueryList, AfterContentInit } from '@angular/core';
import { ChildComponent } from './child.component';
@Component({
selector: 'exe-parent',
template: `
Parent Component
`
})
export class ParentComponent implements AfterContentInit {
@ContentChildren(ChildComponent)
childCmps: QueryList;
ngAfterContentInit() {
console.dir(this.childCmps);
}
}
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
Welcome to Angular World
`,
})
export class AppComponent { }
以上代码运行后,控制台的输出结果:
ContentChild 接口及装饰器
ContentChildDecorator 接口
export interface ContentChildDecorator {
// Type类型:@ContentChild(ChildComponent)
(selector: Type|Function|string, {read}?: {read?: any}): any;
new (selector: Type|Function|string, {read}?: {read?: any}): ContentChild;
}
ContentChildDecorator 装饰器
export const ContentChild: ContentChildDecorator = makePropDecorator(
'ContentChild',
[
['selector', undefined], {
first: true,
isViewQuery: false, // ViewChild或ViewChildren装饰器时为true
descendants: true,
read: undefined,
}
],
Query);
我有话说
ContentChildren 与 ViewChildren 的定义
在 host 元素
和标签中被称为 Content Children
在组件的模板中定义的内容,它是组件的一部分,被称为 View Children
ContentChild 与 ViewChild 的异同点
相同点
都是属性装饰器
都有对应的复数形式装饰器:ContentChildren、ViewChildren
都支持 Type
|Function|string 类型的选择器
不同点
ContentChild 用来从通过 Content Projection 方式 (ng-content) 设置的视图中获取匹配的元素
ViewChild 用来从模板视图中获取匹配的元素
在父组件的 ngAfterContentInit 生命周期钩子中才能成功获取通过 ContentChild 查询的元素
在父组件的 ngAfterViewInit 生命周期钩子中才能成功获取通过 ViewChild 查询的元素
在 Root Component 中无法使用 `ng-content
原因主要有以下几点:
标签之间的信息是用来表明 Angular 的应用程序正在启动中Angular 2 编译器不会处理
index.html
文件中设置的绑定信息,另外出于安全因素考虑,为了避免index.html
中的{{}}
插值,被服务端使用的模板引擎处理。
总结
本文先通过一个简单的需求,引出了 Angular 2 中的 ng-content
指令,进而介绍了 Angular Content Projection (内容投影) 的概念,最后才正式介绍 ContentChild 和 ContentChildren 属性装饰器。另外在文末我们也介绍了 ContentChild 与 ViewChild 的异同点,希望本文能帮助读者更好地理解相关的知识点。