在介绍 Angular Directive Lifecycle (生命周期) 之前,我们先来介绍一下 Angular 中 Directive (指令) 与 Component (组件) 的关系。
我们再来看一下 Angular 中定义的指令和组件接口:
// angular2/packages/core/src/Metadata/directives.ts export interface Directive { selector?: string; // 用于定义组件在HTML代码中匹配的标签 inputs?: string[]; // 指令的输入属性 outputs?: string[]; // 指令的输出属性 host?: {[key: string]: string}; // 绑定宿主的属性、事件等 providers?: Provider[]; // 设置指令及其子指令可以用的服务 exportAs?: string; // 导出指令,使得可以在模板中调用 queries?: {[key: string]: any}; // 设置指令的查询条件 } export interface Component extends Directive { changeDetection?: ChangeDetectionStrategy; // 指定组件使用的变化检测策略 viewProviders?: Provider[]; // 设置组件及其子组件(不含ContentChildren)可以用的服务 moduleId?: string; // 包含该组件模块的 id,它被用于解析 模版和样式的相对路径 templateUrl?: string; // 为组件指定一个外部模板的URL地址 template?: string; // 为组件指定一个内联的模板 styleUrls?: string[]; // 为组件指定一系列用于该组件的样式表文件 styles?: string[]; // 为组件指定内联样式 animations?: any[]; // 设置组件相关动画 encapsulation?: ViewEncapsulation; // 设置组件视图包装选项 interpolation?: [string,string]; // 设置默认的插值运算符,默认是"{{"和"}}" entryComponents?: Array<Type<any>|any[]>; // 设置需要被提前编译的组件 }
通过观察上图与 Angular 中指令与组件的接口定义,我们可以总结出指令与组件之间的关系:组件继承于指令,并扩展了与 UI 视图相关的属性,如 template、styles、animations、encapsulation 等。
下面我们进入正题,开始介绍 Angular 指令的生命周期,它是用来记录指令从创建、应用及销毁的过程。Angular 提供了一系列与指令生命周期相关的钩子,便于我们监控指令生命周期的变化,并执行相关的操作。Angular 中所有的钩子如下图所示:
怎么那么多钩子,是不是被吓到了,没事我们基于指令与组件的区别来分个类:
-
指令与组件共有的钩子
ngOnChanges
ngOnInit
ngDoCheck
ngOnDestroy
-
组件特有的钩子
ngAfterContentInit
ngAfterContentChecked
ngAfterViewInit
ngAfterViewChecked
生命周期的顺序
ngOnChanges()
当被绑定的输入属性的值发生变化时调用,该方法接受当前和上一属性值的SimpleChanges对象,首次调用一定会发生在ngOnInit()之前。
son.component.ts
export class SonComponent implements OnChanges { @Input() receive; sonText = 22222; ngOnChanges(changes: SimpleChanges) { console.log(111); console.log(changes.receive.prevIoUsValue); } }
app.component.ts
export class AppComponent implements OnInit { father = 2222; ngOnInit() { setInterval(() => { this.father = Math.random() * 1000; },1000); } }
html
<app-son [receive] = "father"></app-son> <input type="text" [(ngModel)] = "receive"> <input type="text" [(ngModel)] = "sonText">
ngOnChanges()方法获取了一个对象,它把每个发生变化的属性名都映射到了一个SimpleChange对象, 该对象中有属性的当前值和前一个值。
Angular只会在输入属性的值变化时调用这个钩子。 而sonText不是输入属性,Angular不会关注这个属性的变化。
ngOnInit()
在第一轮ngOnChanges()完成之后调用,只调用一次。
使用ngOnInit()有两个原因:
不要在组件的构造函数中获取数据,一般用于依赖注入或执行简单的数据初始化操作。
ngOnInit()是组件获取初始数据的好地方。
另外还要记住,在指令的构造函数完成之前,那些被绑定的输入属性还都没有值。 如果我们需要基于这些属性的值来初始化这个指令,这种情况就会出问题。 而当ngOnInit()执行的时候,这些属性都已经被正确的赋值过了。
我们访问这些属性的第一次机会,实际上是ngOnChanges()方法,Angular会在ngOnInit()之前调用它。 但是在那之后,Angular还会调用ngOnChanges()很多次。而ngOnInit()只会被调用一次。
你可以信任Angular会在创建组件后立刻调用ngOnInit()方法。 这里是放置复杂初始化逻辑的好地方。
ngDoCheck()
用这个方法来检测那些被Angular忽略的更改。在每个Angular变更检测周期中调用,ngOnChanges()和ngOnInit()之后。
son.component.ts
export class SonComponent implements DoCheck{ @Input() receive; sonText = 22222; ngDoCheck() { console.log(1); } }
虽然ngDoCheck()钩子可以监测到属性值什么时候发生了变化。但我们必须小心。 这个ngDoCheck钩子被非常频繁的调用,在每次变更检测周期之后,发生了变化的每个地方都会调它。
ngAfterContentInit()
当把内容投影进组件之后调用。第一次ngDoCheck()之后调用,只调用一次。只适用于组件。
ngAfterContentChecked()
每次完成被投影组件内容的变更检测之后调用。ngAfterContentInit()和每次ngDoCheck()之后调用。只适合组件。
ngAfterViewInit()
初始化完组件视图及其子视图之后调用。第一次ngAfterContentChecked()之后调用,只调用一次。只适合组件。
ngAfterViewChecked()
每次做完组件视图和子视图的变更检测之后调用。ngAfterViewInit()和每次ngAfterContentChecked()之后调用。只适合组件。
ngOnDestroy
当Angular每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。在Angular销毁指令/组件之前调用。