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