文章目录
生命周期钩子
生命周期的顺序
- ngOnChanges():当 Angular 设置或重新设置数据绑定的输入属性时响应。 该方法接受当前和上一属性值的 SimpleChanges 对象。(与Vue中的computed类似)
- ngOnInit():在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。在第一轮 ngOnChanges() 完成之后调用,只调用一次。
- ngDoCheck():检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应。紧跟在每次执行变更检测时的 。(与Vue中的watch类似),ngOnChanges() 和 首次执行变更检测时的 ngOnInit() 后调用。
- ngAfterContentInit():当 Angular 把外部内容投影进组件视图或指令所在的视图之后(类似于Vue中的slot插槽)调用。第一次 ngDoCheck() 之后调用,只调用一次。
- ngAfterContentChecked():每当 Angular 检查完被投影到组件或指令中的内容之后调用。ngAfterContentInit() 和每次 ngDoCheck() 之后调用
- ngAfterViewInit():当 Angular 初始化完组件视图及其子视图或包含该指令的视图之后调用。第一次 ngAfterContentChecked() 之后调用,只调用一次。
- ngAfterViewChecked():每当 Angular 做完组件视图和子视图或包含该指令的视图的变更检测之后调用。ngAfterViewInit() 和每次 ngAfterContentChecked() 之后调用。
- ngOnDestroy():每当 Angular 每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。在 Angular 销毁指令或组件之前立即调用。
初始化组件或指令
使用 ngOnInit() 方法执行以下初始化任务。
- 在构造函数外部执行复杂的初始化。组件的构造应该既便宜又安全。比如,你不应该在组件构造函数中获取数据。ngOnInit() 是组件获取初始数据的好地方。
- 只有在构造完成之后才会设置指令的数据绑定输入属性。如果要根据这些属性对指令进行初始化,请在运行 ngOnInit() 时设置它们。
在实例销毁时进行清理
把清理逻辑放进 ngOnDestroy() 中,这个逻辑就必然会在 Angular 销毁该指令之前运行。
- 释放资源,这些资源不会自动被垃圾回收。如果你不这样做,就存在内存泄漏的风险。
- 取消订阅可观察对象和 DOM 事件。
- 停止 interval 计时器。
- 反注册该指令在全局或应用服务中注册过的所有回调。
- 用来通知应用程序的其它部分,该组件即将消失。
一般性例子(见官方文档)
所有生命周期事件的顺序和频率
Peek-A-Boo
顺序:
OnChanges
OnInit
DoCheck
AfterContentInit
AfterContentChecked
AfterViewInit
AfterViewChecked
OnDestroy
频率:
- 点击update hero按钮时,执行顺序:
DoCheck
AfterContentChecked
AfterViewChecked
OnChanges: name changed to “Windstorm11!”
DoCheck
AfterContentChecked
AfterViewChecked - 点击destroy按钮时,执行顺序:
DoCheck
AfterContentChecked
AfterViewChecked
OnDestroy
- 当数据更新时,OnChanges会触发一次,DoCheck、AfterContentChecked、AfterViewChecked会在OnChanges的前后触发两次;当销毁数据时,OnDestroy会触发一次。
- DoCheck、AfterContentChecked 和 AfterViewChecked 钩子。 注意,这三种钩子被触发了很多次,所以让它们的逻辑尽可能保持精简是非常重要的!
使用指令来监视 DOM
<div class="parent">
<input [(ngModel)]="newName" (keyup.enter)="addHero()">
<button (click)="addHero()">Add Hero</button>
<button (click)="reset()">Reset Heroes</button>
<p></p>
<div *ngFor="let hero of heroes" mySpy class="heroes">
{{hero}}
</div>
<h4>-- Spy Lifecycle Hook Log --</h4>
<div *ngFor="let msg of logger.logs">{{msg}}</div>
</div>
@Directive({selector: '[mySpy]'})
export class SpyDirective implements OnInit, OnDestroy {
constructor(private logger: LoggerService) { }
ngOnInit() { this.logIt(`onInit`); }
ngOnDestroy() { this.logIt(`onDestroy`); }
private logIt(msg: string) {
this.logger.log(`Spy #${nextId++} ${msg}`);
}
}
通过mySpy指令,监听DOM元素的初始化和销毁。
使用变更检测钩子
<div class="parent">
<h2>{{title}}</h2>
<table>
<tr><td>Power: </td><td><input [(ngModel)]="power"></td></tr>
<tr><td>Hero.name: </td><td><input [(ngModel)]="hero.name"></td></tr>
</table>
<p><button (click)="reset()">Reset Log</button></p>
<on-changes [hero]="hero" [power]="power"></on-changes>
</div>
export class OnChangesComponent implements OnChanges {
@Input() hero: Hero;
@Input() power: string;
changeLog: string[] = [];
ngOnChanges(changes: SimpleChanges) {
for (let propName in changes) {
let chng = changes[propName];
let cur = JSON.stringify(chng.currentValue);
let prev = JSON.stringify(chng.previousValue);
this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
reset() { this.changeLog = []; }
}
- ngOnChanges监听到了@input输入属性的变化,并分别记录了变量变化前和变化后的值。
- ngOnChanges只能监听到当前变量(如power和hero)的变化,无法监听当前变量的属性的值(hero.name)的变化
响应视图的变更
AfterView 例子展示了 AfterViewInit() 和 AfterViewChecked() 钩子,Angular 会在每次创建了组件的子组件视图后调用它们。
template: `
<div>-- child view begins --</div>
<app-child-view></app-child-view>
<div>-- child view ends --</div>`
@Component({
selector: 'app-child-view',
template: '<input [(ngModel)]="hero">'
})
export class ChildViewComponent {
hero = 'Magneta';
}
export class AfterViewComponent implements AfterViewChecked, AfterViewInit {
private prevHero = '';
// Query for a VIEW child of type `ChildViewComponent`
@ViewChild(ChildViewComponent) viewChild: ChildViewComponent;
ngAfterViewInit() {
// viewChild is set after the view has been initialized
this.logIt('AfterViewInit');
}
ngAfterViewChecked() {
// viewChild is updated after the view has been checked
if (this.prevHero === this.viewChild.hero) {
this.logIt('AfterViewChecked (no change)');
} else {
this.prevHero = this.viewChild.hero;
this.logIt('AfterViewChecked');
}
}
}
下列钩子基于子视图中的每一次数据变更采取行动,它只能通过带@ViewChild装饰器的属性来访问子视图。
- ngAfterViewInit在子视图初始化时触发
- ngAfterViewChecked在子视图变更时触发
响应被投影内容的变更
类似于Vue中的slot插槽
<ng-content> 标签是外来内容的占位符。 它告诉 Angular 在哪里插入这些外来内容。 在这里,被投影进去的内容就是来自父组件的 <app-child> 标签。
export class AfterContentComponent implements AfterContentChecked, AfterContentInit {
private prevHero = '';
comment = '';
// Query for a CONTENT child of type `ChildComponent`
@ContentChild(ChildComponent) contentChild: ChildComponent;
ngAfterContentInit() {
// contentChild is set after the content has been initialized
this.logIt('AfterContentInit');
this.doSomething();
}
ngAfterContentChecked() {
// contentChild is updated after the content has been checked
if (this.prevHero === this.contentChild.hero) {
this.logIt('AfterContentChecked (no change)');
} else {
this.prevHero = this.contentChild.hero;
this.logIt('AfterContentChecked');
this.doSomething();
}
}
// ...
}
- 只能通过带有@ContentChild装饰器的属性来查询到“子级内容”。
- AfterContent 在AfterView 之前触发
自定义变更检测逻辑
使用 ngDoCheck() 钩子来检测和处理 Angular 自己没有捕捉到的变化。这个钩子触发的很频繁,类似于Vue中的watch。
ngDoCheck() {
if (this.hero.name !== this.oldHeroName) {
this.changeDetected = true;
this.changeLog.push(`DoCheck: Hero name changed to "${this.hero.name}" from "${this.oldHeroName}"`);
this.oldHeroName = this.hero.name;
}
if (this.power !== this.oldPower) {
this.changeDetected = true;
this.changeLog.push(`DoCheck: Power changed to "${this.power}" from "${this.oldPower}"`);
this.oldPower = this.power;
}
if (this.changeDetected) {
this.noChangeCount = 0;
} else {
// log that hook was called when there was no relevant change.
let count = this.noChangeCount += 1;
let noChangeMsg = `DoCheck called ${count}x when no change to hero or power`;
if (count === 1) {
// add new "no change" message
this.changeLog.push(noChangeMsg);
} else {
// update last "no change" message
this.changeLog[this.changeLog.length - 1] = noChangeMsg;
}
}
this.changeDetected = false;
}