1,
指令和组件的实例有一个生命周期:当 Angular 新建、更新和销毁它们时触发。 通过实现一个或多个 Angular core
库里定义的生命周期钩子接口,开发者可以介入该生命周期中的这些关键时刻。
每个接口都有唯一的一个钩子方法,它们的名字是由接口名再加上 ng
前缀构成的。比如,OnInit
接口的钩子方法叫做 ngOnInit
, Angular 在创建组件后立刻调用它,:
生命周期的顺序
当 Angular 使用构造函数新建一个组件或指令后,就会按下面的顺序在特定时刻调用这些生命周期钩子方法:
钩子 | 用途及时机 |
---|---|
| 当 Angular(重新)设置数据绑定输入属性时响应。 该方法接受当前和上一属性值的 在 |
| 在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。 在第一轮 |
| 检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应。 在每个变更检测周期中,紧跟在 |
当 Angular 把外部内容投影进组件/指令的视图之后调用。 第一次 | |
| 每当 Angular 完成被投影组件内容的变更检测之后调用。
|
当 Angular 初始化完组件视图及其子视图之后调用。 第一次 | |
| 每当 Angular 做完组件视图和子视图的变更检测之后调用。
|
| 每当 Angular 每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。 在 Angular 销毁指令/组件之前调用。 |
//探测指令
// Spy on any element to which it is applied.
// Usage: <div mySpy>...</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}`);
}
}
你可以把这个侦探指令写到任何原生元素或组件元素上,它将与所在的组件同时初始化和销毁。 下面是把它附加到用来重复显示英雄数据的这个 <div>
上。
<div *ngFor="let hero of heroes" mySpy class="heroes">
{{hero}}
</div>
OnInit()
使用 ngOnInit()
有两个原因:
-
在构造函数之后马上执行复杂的初始化逻辑
-
在 Angular 设置完输入属性之后,对该组件进行准备。
不要在组件的构造函数中获取数据? 在测试环境下新建组件时或在你决定要显示它之前,不应该担心它会尝试联系远程服务器。 构造函数中除了使用简单的值对局部变量进行初始化之外,什么都不应该做。
另外还要记住,在指令的构造函数完成之前,那些被绑定的输入属性还都没有值。 如果你需要基于这些属性的值来初始化这个指令,这种情况就会出问题。 而当 ngOnInit()
执行的时候,这些属性都已经被正确的赋值过了。
ngOnChanges()
方法是你访问这些属性的第一次机会。Angular 会在 ngOnInit()
之前调用 ngOnChanges()
,之后还会调用很多次。但只会调用一次 ngOnInit()
。
OnChanges()
一旦检测到该组件(或指令)的输入属性发生了变化,Angular 就会调用它的 ngOnChanges()
方法。 本例监控 OnChanges
钩子。
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}`);
}
}
DoCheck()
使用 DoCheck
钩子来检测那些 Angular 自身无法捕获的变更并采取行动
用这个方法来检测那些被 Angular 忽略的更改。
虽然 ngDoCheck()
钩子可以可以监测到英雄的 name
什么时候发生了变化。但其开销很恐怖。 这个 ngDoCheck
钩子被非常频繁的调用 —— 在每次变更检测周期之后,发生了变化的每个地方都会调它。 在这个例子中,用户还没有做任何操作之前,它就被调用了超过二十次。
AfterView
AfterView 例子展示了 AfterViewInit()
和 AfterViewChecked()
钩子,Angular 会在每次创建了组件的子视图后调用它们。
下面是一个子视图,它用来把英雄的名字显示在一个 <input>
中:
@Component({
selector: 'app-child-view',
template: '<input [(ngModel)]="hero">'
})
export class ChildViewComponent {
hero = 'Magneta';
}
注意,Angular 会频繁的调用 AfterViewChecked()
,甚至在并没有需要关注的更改时也会触发。 所以务必把这个钩子方法写得尽可能精简,以免出现性能问题。
AfterContent
AfterContent 例子展示了 AfterContentInit()
和 AfterContentChecked()
钩子,Angular 会在外来内容被投影到组件中之后调用它们。
内容投影
内容投影是从组件外部导入 HTML 内容,并把它插入在组件模板中指定位置上的一种途径。
下列迹象表明存在着内容投影:
-
在组件的元素标签中有 HTML
-
组件的模板中出现了
<ng-content>
标签
AfterContent 钩子
AfterContent 钩子和 AfterView 相似。关键的不同点是子组件的类型不同。
-
AfterView 钩子所关心的是
ViewChildren
,这些子组件的元素标签会出现在该组件的模板里面。 -
AfterContent 钩子所关心的是
ContentChildren
,这些子组件被 Angular 投影进该组件中。
使用 AfterContent 时,无需担心单向数据流规则
该组件的 doSomething()
方法立即更新了组件被绑定的 comment
属性。 它不用等下一回合。
回忆一下,Angular 在每次调用 AfterView 钩子之前也会同时调用 AfterContent。 Angular 在完成当前组件的视图合成之前,就已经完成了被投影内容的合成。 所以你仍然有机会去修改那个视图。