每个组件都有一个被Angular管理的生命周期,Angular提供了生命周期钩子,把这些关键生命时刻暴露出来,赋予我们在它们发生时采取行动的能力。
钩子 | 目的和时机 |
---|---|
ngOnChanges() | 当Angular(重新)设置数据绑定输入属性时响应。 该方法接受当前和上一属性值的SimpleChanges对象当被绑定的输入属性的值发生变化时调用,首次调用一定会发生在ngOnInit()之前。 |
ngOnInit() | 在Angular第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。在第一轮ngOnChanges()完成之后调用,只调用一次。 |
ngDoCheck() | 检测,并在发生Angular无法或不愿意自己检测的变化时作出反应。在每个Angular变更检测周期中调用,ngOnChanges()和ngOnInit()之后. |
ngAfterContentInit() | 当把内容投影进组件之后调用。第一次ngDoCheck()之后调用,只调用一次。只适用于组件。 |
ngAfterContentChecked() | 每次完成被投影组件内容的变更检测之后调用,ngAfterContentInit()和每次ngDoCheck()之后调用只适合组件。 |
ngAfterViewInit() | 每次做完组件视图和子视图的变更检测之后调用。ngAfterViewInit()和每次ngAfterContentChecked()之后调用。只适合组件。 |
ngAfterViewChecked() | 每次做完组件视图和子视图的变更检测之后调用。ngAfterViewInit()和每次ngAfterContentChecked()之后调用。只适合组件。 |
ngOnDestroy() | 当Angular每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。在Angular销毁指令/组件之前调用。 |
下面我们来看代码
// 子组件
import {AfterContentChecked,AfterContentInit,AfterViewChecked,AfterViewInit,
DoCheck, OnChanges, OnDestroy, OnInit,SimpleChange} from '@angular/core';
import { Component, Input } from '@angular/core';
import { LoggerService } from './logger.service';
let nextId = 1;
export class PeekABoo implements OnInit {
constructor(private logger: LoggerService) { }
// 在组件首次加载的时候执行,在第一次ngOnchange后
ngOnInit() { this.logIt(`OnInit`); }
logIt(msg: string) {
this.logger.log(`#${nextId++} ${msg}`);
}
}
@Component({
selector: 'peek-a-boo',
template: '<p>Now you see my hero, {{name}}</p>',
styles: ['p {background: LightYellow; padding: 8px}']
})
export class PeekABooComponent extends PeekABoo implements
OnChanges, OnInit, DoCheck,
AfterContentInit, AfterContentChecked,
AfterViewInit, AfterViewChecked,
OnDestroy {
@Input() name: string;
private verb = 'initialized';
constructor(logger: LoggerService) {
super(logger);
let is = this.name ? 'is' : 'is not';
this.logIt(`name ${is} known at construction`);
}
// 当父组件的变量变化时,自组建的属性也会改变.
ngOnChanges(changes: SimpleChanges) {
let changesMsgs: string[] = [];
for (let propName in changes) {
if (propName === 'name') {
let name = changes['name'].currentValue;
changesMsgs.push(`name ${this.verb} to "${name}"`);
} else {
changesMsgs.push(propName + ' ' + this.verb);
}
}
this.logIt(`OnChanges: ${changesMsgs.join('; ')}`);
this.verb = 'changed'; // next time it will be a change
}
// 在每次需要做出改变的时候的触发
ngDoCheck() { this.logIt(`DoCheck`); }
// 组件内容初始化,只在组件周期发生一次
ngAfterContentInit() { this.logIt(`AfterContentInit`); }
// 在ngoncheck之后发生
ngAfterContentChecked() { this.logIt(`AfterContentChecked`); }
ngAfterViewInit() { this.logIt(`AfterViewInit`); }
ngAfterViewChecked() { this.logIt(`AfterViewChecked`); }
// 组件销毁
ngOnDestroy() { this.logIt(`OnDestroy`); }
}
service代码是输出打印信息的
import { Injectable } from '@angular/core';
@Injectable()
export class LoggerService {
logs: string[] = [];
prevMsg = '';
prevMsgCount = 1;
log(msg: string) {
if (msg === this.prevMsg) {
// 若之前的msg和新的msg相同时,count加1.
this.logs[this.logs.length - 1] = msg + ` (${this.prevMsgCount += 1}x)`;
} else {
// New message; log it.
this.prevMsg = msg;
this.prevMsgCount = 1;
this.logs.push(msg);
}
}
clear() { this.logs.length = 0; }
tick() { this.tick_then(() => { }); }
tick_then(fn: () => any) { setTimeout(fn, 0); }
}
我们很奇怪的子组件是如何来使用services的,来看父组件
// 父组件
import { Component } from '@angular/core';
import { LoggerService } from './logger.service';
@Component({
selector: 'peek-a-boo-parent',
template: `
<div class="parent">
<h2>Peek-A-Boo</h2>
// 控制子组件的显影
<button (click)="toggleChild()">
{{hasChild ? 'Destroy' : 'Create'}} PeekABooComponent
</button>
<button (click)="updateHero()" [hidden]="!hasChild">Update Hero</button>
<peek-a-boo *ngIf="hasChild" [name]="heroName">
</peek-a-boo>
<h4>-- Lifecycle Hook Log --</h4>
<div *ngFor="let msg of hookLog">{{msg}}</div>
</div>
`,
styles: ['.parent {background: moccasin}'],
providers: [ LoggerService ] //父组件的service可供子组件使用
})
export class PeekABooParentComponent {
hasChild = false;
hookLog: string[];
heroName = 'Windstorm';
private logger: LoggerService;
constructor(logger: LoggerService) {
this.logger = logger;
this.hookLog = logger.logs;
}
toggleChild() {
this.hasChild = !this.hasChild;
if (this.hasChild) {
this.heroName = 'Windstorm';
this.logger.clear(); // clear log on create
}
this.logger.tick();
}
updateHero() {
this.heroName += '!';
this.logger.tick();
}
}
下面我们来看一下好玩的东西。。。
import { Directive, OnInit, OnDestroy } from '@angular/core';
import { LoggerService } from './logger.service';
let nextId = 1;
// 在这里定义了一个指令,上面有2个钩子函数
@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}`);
}
}
究竟上面的代码有何用意?
// spy.component.html
<div class="parent">
<h2>Spy Directive</h2>
<input [(ngModel)]="newName" (keyup.enter)="addHero()">
<button (click)="addHero()">Add Hero</button>
<button (click)="reset()">Reset Heroes</button>
<p></p>
//这一步很关键,这些个div具有了生命周期钩子,从myspy获得
<div *ngFor="let hero of heroes" mySpy class="heroes">
{{hero}}
</div>
<h4>-- Spy Lifecycle Hook Log --</h4>
<div *ngFor="let msg of spyLog">{{msg}}</div>
</div>
@Component({
selector: 'spy-parent',
templateUrl: './spy.component.html',
styles: [
'.parent {background: khaki;}',
'.heroes {background: LightYellow; padding: 0 8px}'
],
providers: [LoggerService]
})
export class SpyParentComponent {
newName = 'Herbie';
heroes: string[] = ['Windstorm', 'Magneta'];
spyLog: string[];
constructor(private logger: LoggerService) {
this.spyLog = logger.logs;
}
addHero() {
if (this.newName.trim()) {
this.heroes.push(this.newName.trim());
this.newName = '';
this.logger.tick();
}
}
removeHero(hero: string) {
this.heroes.splice(this.heroes.indexOf(hero), 1);
this.logger.tick();
}
reset() {
this.logger.log('-- reset --');
this.heroes.length = 0;
this.logger.tick();
}
}
点击add时,添加一个英雄,会打印ngoninit
我们接着来看一个很好解释ngonchange的例子。
// parent.html
<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.name" [power]="power"></on-changes>
</div>
下面这个代码很长,仔细看
import {
Component, Input, OnChanges,
SimpleChanges, ViewChild
} from '@angular/core';
class Hero {
constructor(public name: string) {}
}
@Component({
selector: 'on-changes',
template: `
<div class="hero">
<p>{{hero}} can {{power}}</p>
<h4>-- Change Log --</h4>
<div *ngFor="let chg of changeLog">{{chg}}</div>
</div>
`,
styles: [
'.hero {background: LightYellow; padding: 8px; margin-top: 8px}',
'p {background: Yellow; padding: 8px; margin-top: 8px}'
]
})
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.length = 0; }
}
/***************************************/
@Component({
selector: 'on-changes-parent',
templateUrl: './on-changes-parent.component.html',
styles: ['.parent {background: Lavender;}']
})
export class OnChangesParentComponent {
hero: Hero;
power: string;
title = 'OnChanges';
@ViewChild(OnChangesComponent) childView: OnChangesComponent;
constructor() {
this.reset();
}
reset() {
// new Hero object every time; triggers onChanges
this.hero = new Hero('Windstorm');
// setting power only triggers onChanges if this value is different
this.power = 'sing';
if (this.childView) { this.childView.reset(); }
}
}
有关viewChild在以前的章节已经讲过,可以回看之前的文章。需要注意的是这种吧2个组件写在一起的形式。
上面讲述了3个生命周期钩子函数调用的例子,还需自己去体味才行。