子组件
<div>
<label for="username">名</label>
<input id="username" type="text" [(ngModel)]="username" (input)="onUsernameInput($event)">
<!-- 使用ngModel必须在当前模块的module.ts中的import数组中加入FormsModule-->
<div>
<span>姓名</span>
<span>{{surname}}</span>
<span>{{username}}</span>
</div>
</div>
import { Component, Input, Output, EventEmitter, OnInit, SimpleChanges, OnChanges, DoCheck, AfterContentInit, AfterContentChecked } from '@angular/core';
@Component({
selector: 'app-sub',
templateUrl: './sub.component.html',
styleUrls: ['./sub.component.sass']
})
export class SubComponent implements OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked {
@Input() surname: string;
@Output() usernameChange = new EventEmitter<string>();
username: string = '';
// 属性与方法最先被定义
constructor() {
this.log('declaration');
this.log('constructor');
}
log(str) {
const spaceStr = ' ';
console.log(`${spaceStr}${(str + spaceStr + ' ').substr(0, 14)} sub.surname='${this.surname}' sub.username='${this.username}'`);
}
onUsernameInput($event) {
console.log('子组件输入框发生了变化' + $event.target.value)
this.usernameChange.emit($event.target.value);
}
// 当 Angular 设置或重新设置数据绑定的输入属性时响应
// 时机 ngOnInit之前 或 输入属性的值发生变化后 如果没有输入属性 则不调用这个方法
ngOnChanges(changes: SimpleChanges) {
const changesRecord = []
for (const propName in changes) {
const chng = changes[propName];
const cur = JSON.stringify(chng.currentValue);
const prev = JSON.stringify(chng.previousValue);
changesRecord.push(`输入属性${propName}: ${prev} --> ${cur}`);
}
this.log('ngOnChanges'); // 因为组件没有input输入属性 所以不会调用ngOnChanges
}
// 在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后 用于初始化指令/组件
// 时机 在第一轮 ngOnChanges() 完成之后调用
ngOnInit(): void {
this.username = 'jin';
console.log('给username赋值"jin"');
this.log('ngOnInit')
}
// 检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应
// 时机 ngOnInit或ngOnChanges之后
ngDoCheck() {
this.log('ngDoCheck');
}
// 当 Angular 把外部内容投影进组件视图或指令所在的视图之后调用
// 时机 第一次 ngDoCheck() 之后
ngAfterContentInit() {
this.log('ngAfterContentInit');
}
ngAfterContentChecked() {
this.log('ngAfterContentChecked')
}
}
父组件
<div>
<h1>父组件</h1>
<label for="surname">姓</label>
<input id="surname" type="text" [(ngModel)]="surname" (input)="onInput($event)" (blur)="onBlur($event)">
<div>
<span>姓名</span>
<span>{{surname}}</span>
<span>{{username}}</span>
</div>
<h2>子组件</h2>
<app-sub [surname]="surname" (usernameChange)="onSubUsernameChange($event)"></app-sub>
</div>
import { Component, OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked } from '@angular/core';
@Component({
selector: 'app-life-cycle',
templateUrl: './life-cycle.component.html',
styleUrls: ['./life-cycle.component.sass']
})
export class LifeCycleComponent implements OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked {
surname: string;
username: string;
// 属性与方法最先被定义
constructor() {
console.log('父组件\t\t\t\t\t子组件')
this.log('declaration');
this.log('constructor');
}
log(str) {
const spaceStr = ' ';
console.log(`${(str + spaceStr).substr(0, 44)}life.surname='${this.surname}' life.username='${this.username}'`);
}
onInput($event) {
console.log('父组件输入框追加输入了' + $event.data)
}
onBlur($event) {
console.log('父组件输入框失去焦点')
}
onSubUsernameChange(value: string){
this.username = value;
}
ngOnChanges() {
this.log('ngOnChanges'); // 因为组件没有input输入属性 所以不会调用ngOnChanges
}
// 在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后 用于初始化指令/组件
// 时机 在第一轮 ngOnChanges() 完成之后调用
ngOnInit(): void {
this.surname = 'h';
console.log('给surname赋值"x"');
this.log(`ngOnInit`)
}
// 检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应
// 时机 ngOnInit或ngOnChanges之后
ngDoCheck() {
this.log(`ngDoCheck`);
}
// 当 Angular 把外部内容投射进组件视图或指令所在的视图之后调用
// 时机 第一次 ngDoCheck() 之后
ngAfterContentInit() {
this.log('ngAfterContentInit');
}
// 每当 Angular 检查完被投射到组件或指令中的内容之后调用
// ngAfterContentInit() 和每次 ngDoCheck() 之后调用
ngAfterContentChecked() {
this.log('ngAfterContentChecked')
}
// 当 Angular 初始化完组件视图及其子视图或包含该指令的视图之后调用
// 第一次 ngAfterContentChecked() 之后调用
ngAfterViewInit() {
this.log('ngAfterViewInit')
}
// 每当 Angular 做完组件视图和子视图或包含该指令的视图的变更检测之后调用。
// ngAfterViewInit() 和每次 ngAfterContentChecked() 之后调用
ngAfterViewChecked() {
this.log('ngAfterViewChecked')
}
}
操作:先在父组件输入框追加字符u,然后在子组件输入框追加字符g。现象如下:
分析如下:
序号 | 父组件 | 子组件 | 发生的变化 | 备注 |
---|---|---|---|---|
1 | declaration | life.surname:'undefined' life.username:'undefined' | 定义组件类的属性与方法 | |
2 | constructor | |||
3 |
| declaration | sub.surname:'undefined' sub.username:'' | |
4 | constructor |
| ||
5 | ngOnInit | life.surname:'h' | 这里执行了this.surname = 'h' | |
6 | ngDoCheck | 变更检测 | ||
7 | ngAfterContentInit | 把外部内容投射进组件视图或指令所在的视图之后调用 | ||
8 | ngAfterContentChecked | 检查完被投射到组件或指令中的内容之后调用。 | ||
9 | ngOnChanges | sub.surname:'h' | 设置或重新设置数据绑定的输入属性时响应 | |
10 | ngOnInit | sub.username:'jin' | 这里执行了this.surname = 'jin' | |
11 | ngDoCheck | |||
12 | ngAfterContentInit | |||
13 | ngAfterContentChecked | |||
14 | ngAfterViewInit | 初始化完组件视图及其子视图或包含该指令的视图之后调用 | ||
15 | ngAfterViewChecked | 做完组件视图和子视图或包含该指令的视图的变更检测之后调用 | ||
16 | ngDoCheck | |||
17 | ngAfterContentChecked | |||
18 | ngDoCheck | |||
19 | ngAfterContentChecked | |||
20 | ngAfterViewChecked |
序号 | 父组件 | 子组件 | 发生的变化 | 备注 |
---|---|---|---|---|
21 | ngDoCheck | life.surname:hu | ||
22 | ngAfterContentChecked | |||
23 | ngOnChanges | sub.surname:'hu' | ||
24 | ngDoCheck | |||
25 | ngAfterContentChecked | |||
26 | ngAfterViewChecked |
序号 | 父组件 | 子组件 | 发生的变化 | 备注 |
---|---|---|---|---|
27 | ngDoCheck | |||
28 | ngAfterContentChecked | |||
29 | ngDoCheck | |||
30 | ngAfterContentChecked | |||
31 | ngAfterViewChecked |
序号 | 父组件 | 子组件 | 发生的变化 | 备注 |
---|---|---|---|---|
32 | ngDoCheck | |||
33 | ngAfterContentChecked | life.username:'jing' | ||
34 | ngDoCheck | sub.username:'jing' | ||
35 | ngAfterContentChecked | |||
36 | ngAfterViewChecked |
操作1234都基本类似
变更检测的顺序:
都是父组件先ngDoCheck ngAfterContentChecked
然后子组件再ngDoCheck ngAfterContentChecked
最后父组件 ngAfterViewChecked
其中的差异是
操作1:
由于子组件的输入属性发生了变化故 在子组件的ngDoCheck之前有个ngOnChanges
操作2和4:
虽然只是失焦事件 angular也会进行一遍检查
操作3:
子组件由于是抛出了事件 然后父组件接收到该事件再去修改的username属性
特别的是虽然是子组件中的username属性先修改 再触发事件去修改父组件中的username属性
但是 父组件的ngDoCheck要比子组件的ngDoCheck更先检测到username属性的变更
这样就保持了上述所说的变更检测的顺序