ng-content
<ng-content>是angular提供一组标签,简单的说,通过该标签,可以从父组件向子组件的模板中注入内容;也可以将ng-content看作一种特殊的占位符,使用该占位符,可以动态的将任何元素插入到自定义的组件中。类似于在angular模板中通过{{variable}}插入变量。
下面分别使用{{}}和ng-content实现一个button组件,button的label信息从父组件传入:
- 使用{{label}} + @Input,显示从父组件传到子组件的值
// abb-button-a.component.ts
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'add-button-a',
template: `
<button>{{label}}</button>
`
})
export class AddButtonAComponent implements OnInit {
@Input() label: string;
constructor() { }
ngOnInit() {
}
}
上述代码中创建了一个简单的button组件,通过@Input获取父组件传递的label,并通过{{}}表达式将其显示在button上。在app.component.html中加载该组件:
// app.component.html
<add-button-a label='add-button-a'></add-button-a>
- 使用ng-content构建相同的button组件
// add-button-b.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'add-button-b',
template:
`<button>
<ng-content></ng-content>
</button>`,
})
export class AddButtonBComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
在上述add-button-b组件中,不再使用Input获取父组件的值,使用ng-content作为按钮文字的占位符;当需要使用该组件时,可直接在父组件中插入对应的值:
// app.component.html
<add-button-b>
<span>add-button-b</span>
</add-button-b>
对比以上两种方式,通过ng-content定义通用组件时,不要再额外定义变量接受父组件传递的值,仅定义通用的模板,其内部具体的内容在真正使用该组件时再关注。
- multiple ng-content & Selector 在定义通用组件时,如果同时使用多个ng-content占位符,如何确保每个占位符插入准确的内容呢?ng-content支持使用选择器,既可以使用CSS选择器,用户也可自定义选择器。
- CSS 选择器 - 标签选择器和class选择器
改造上述的add-button-b组件, 使其包含两个,分别将标签(span)和class(.text)作为选择器:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'add-button-b',
template:
`<button>
<!-- add the select attribute to ng-content -->
<ng-content select='span'></ng-content>
</button>
<ng-content select='.text'></ng-content>
`,
})
export class AddButtonBComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
在父组件,按照选择器的规则进行插值,span标签及内容将映射到button上,而<div class='text'>Hello World</div>将显示在button下方:
<add-button-b>
<span>add-button-b</span>
<div class='text'>Hello World</div>
</add-button-b>
- 自定义选择器
除了上述CSS选择器外,可以使用方括号[ ]自定义选择器,如下再模板中定义select="[button-label]"及select='[hello-text]':
@Component({
selector: 'add-button-b',
template:
`<button (click)='onClick()'>
<ng-content select="[button-label]"></ng-content>
</button>
<ng-content select='[hello-text]'></ng-content>
`,
})
在父组件中应用该组件时,添加相应的属性button-label和hello-text,即可将相应的内容插入到对应的为止:
<add-button-b>
<span button-label>add-button-b</span>
<div hello-text>Hello World</div>
</add-button-b>
@ContentChild & @ContentChildren
前面已经了解了ng-content的用法,那么如何获取通过ng-content插入到组件中的内容呢?Angular提供了ContentChild和ContentChildren两种装饰器,通过这两种装饰器,可以在类中可以获取通过ng-content插入到组件模板中的任意元素。
以ContentChild为例,它可以接受一系列的参数,各个参数的含义此处不介绍。
下面举个简单的例子,分别创建MessageContainerComponent和MessageComponent,并通过ng-content将MessageComponent插入到MessageContainerComponent的模板中:
// message-container.component.ts
import { Component, OnInit, ContentChild, AfterViewInit } from '@angular/core';
@Component({
selector: 'app-message-container',
template: `
<div>
<span>Message Container</span>
<ng-content></ng-content>
</div>
`,
styleUrls: ['./message-container.component.css']
})
export class MessageContainerComponent implements OnInit {
constructor() { }
ngOnInit() { }
}
// message.component.ts
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'app-message',
template: `
<h2>{{message}}</h2>
`,
styleUrls: ['./message.component.css']
})
export class MessageComponent implements OnInit {
@Input() message;
constructor() { }
ngOnInit() { }
}
在父组件中加载MessageContainerComponent:
<app-message-container>
<app-message message='Hello World!!'></app-message>
</app-message-container>
页面可正常渲染,并将messageComponent加载到messageContainerComponent中。
此时,在MessageContainerComponent类中引入ContentChild,并通过@ContentChild获取MessageComponent对象:
import { Component, OnInit, ContentChild, AfterContentInit } from '@angular/core';
import { MessageComponent } from '../message/message.component';
@Component({
selector: 'app-message-container',
template: `
<div>
<span>Message Container</span>
<ng-content select='app-message'></ng-content>
</div>
`,
styleUrls: ['./message-container.component.css']
})
export class MessageContainerComponent implements OnInit, AfterContentInit {
// 通过@ContentChild创建变量
@ContentChild(MessageComponent, {static: true}) MessageComponent: MessageComponent;
constructor() { }
ngOnInit() { }
//在ngAfterContentInit生命周期函数中获取messageContainerComponentChild对象
ngAfterContentInit() {
console.log(this.messageContainerComponentChild);
}
}

// app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-message-container>
// 生成多个app-message
<app-message *ngFor='let m of messageList' [message]='m'></app-message>
</app-message-container>`,
styleUrls: ['./app.component.css']
})
export class AppComponent {
messageList = ['Hello World', 'Hello Angular'];
}
在MessageContainerComponent通过ContentChildren获取元素集合:
// message-container.component.ts
import { Component, OnInit, ContentChild, AfterContentInit, ContentChildren, QueryList } from '@angular/core';
import { MessageComponent } from '../message/message.component';
@Component({
selector: 'app-message-container',
template: `
<div>
<span>Message Container</span>
<ng-content select='app-message'></ng-content>
</div>
`,
styleUrls: ['./message-container.component.css']
})
export class MessageContainerComponent implements OnInit, AfterContentInit {
// 通过ContentChildren创建包含MessageComponent的元素集合messageContainerComponentChildren
@ContentChildren(MessageComponent) messageContainerComponentChildren: QueryList<MessageComponent>;
constructor() { }
ngOnInit() { }
ngAfterContentInit() {
console.log(this.messageContainerComponentChildren);
}
}
控制台输出元素列表如下:

当使用<ng-content>插入到组件中的不是组件,而是HTML元素时,通过contentChild获取到的自然也不再是实例对象,而是原生DOM元素封装后的引用:
// app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-message-container>
<span #message>Message Info</span>
</app-message-container>`,
styleUrls: ['./app.component.css']
})
export class AppComponent {
}
// message=container.component.ts
import { Component, OnInit, AfterContentInit, ContentChild, ElementRef } from '@angular/core';
import { MessageComponent } from '../message/message.component';
@Component({
selector: 'app-message-container',
template: `
<div>
<span>Message Container</span>
<ng-content></ng-content>
</div>
`,
styleUrls: ['./message-container.component.css']
})
export class MessageContainerComponent implements OnInit, AfterContentInit {
@ContentChild('message', {static: false}) message;
constructor() { }
ngOnInit() { }
ngAfterContentInit() {
console.log(this.message);// ElementRef
console.log(this.message.nativeElement);
}
}
上述代码中,往组件中插入了<span #message>Message Info</span>元素,并以message作为选择器,在MessageContainerComponent中获取该元素,得到的是ElementRef,是在原生DOM元素上的一层封装,可以通过ElementRef.nativeElement获取原生的DOM元素。

<ng-content>插入到组件中的子组件或HTML元素,都可通过ContentChild或ContentChildren获取,插入的子组件渲染完成后,会触发ngAfterContentInit周期函数,因此通常在该生命是周期函数中获取相应组件的实例。
@ViewChild and @ViewChildren
@ViewChild和@ViewChildren也是angular提供的非常实用的装饰器,利用这些装饰器,可以获取到任意模板元素的引用。
- ViewChild 重构前一个例子中的MessageContainerComponent,在其模板直接引入Message组件,并使用ViewChild获取模板中对应元素的引用:
import { Component, OnInit, AfterViewInit, ViewChild } from '@angular/core';
import { MessageComponent } from '../message/message.component';
@Component({
selector: 'app-message-container',
template: `
<div>
<span>Message Container</span>
<h2 #header>Hello World</h2>
<app-message #messageComponent message='Hello Shanghai'></app-message>
</div>
`,
styleUrls: ['./message-container.component.css']
})
export class MessageContainerComponent implements OnInit, AfterViewInit {
// Selector: 'messageComponent'
@ViewChild('messageComponent', {static: false}) messageComponent: MessageComponent;
// Selector: Header
@ViewChild('header', {static: false}) header;
constructor() { }
ngOnInit() { }
ngAfterViewInit() {
console.log(this.messageComponent);
console.log(this.header);
}
}
上述代码中,分别通过ViewChild获取了组件(MessageComponent)和DOM元素(h2)的引用。不同的是,对于组件的引用,获取到的是组件的实例,而对于DOM元素的引用,获取到的是ElementRef,是在原生DOM基础上进行封装的对象,可以通过ElementRef.nativeElement进一步获取原生的DOM对象。
页面渲染完成后,控制台输出如下:

- ViewChildren 从字面上,很容易理解ViewChildren和ViewChild的不同,viewChild获取的是单个元素的引用,而ViewChildren获取的是多个元素引用的集合(QueryList)。
import { Component, OnInit, AfterViewInit, ViewChildren, QueryList } from '@angular/core';
import { MessageComponent } from '../message/message.component';
@Component({
selector: 'app-message-container',
template: `
<div>
<span>Message Container</span>
<app-message #messageComponents *ngFor='let m of messageList' [message]='m'></app-message>
</div>
`,
styleUrls: ['./message-container.component.css']
})
export class MessageContainerComponent implements OnInit, AfterViewInit {
messageList: string[] = ['Hello World', 'Hello Shanghai'];
@ViewChildren('messageComponents') messageComponents: QueryList<MessageComponent>;
constructor() { }
ngOnInit() { }
ngAfterViewInit() {
console.log(this.messageComponents);
}
}
控制台输出如下:

@ContentChild(@ContentChildren) VS @ViewChild(@ViewChildren)
@ContentChild(@ContentChildren)与@ViewChild(@ViewChildren)的使用场景不同:
- @ContentChild(@ContentChildren): 获取组件中直接插入的任何指令、组件或元素,在
ngAfterContentInit生命周期函数中获取; - @ContentChild(@ContentChildren):获取通过
<ng-content>插入到组件中的元素,在ngAfterViewInit生命周期函数中获取。