在介绍 Angular 2 Directive Lifecycle (生命周期) 之前,我们先来介绍一下 Angular 2 中 Directive (指令) 与 Component (组件) 的关系。
我们再来看一下 Angular 2 中定义的指令和组件接口:
// 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 2 中指令与组件的接口定义,我们可以总结出指令与组件之间的关系:组件继承于指令,并扩展了与 UI 视图相关的属性,如 template、styles、animations、encapsulation 等。
下面我们进入正题,开始介绍 Angular 2 指令的生命周期,它是用来记录指令从创建、应用及销毁的过程。Angular 2 提供了一系列与指令生命周期相关的钩子,便于我们监控指令生命周期的变化,并执行相关的操作。Angular 2 中所有的钩子如下图所示:
怎么那么多钩子,是不是被吓到了,没事我们基于指令与组件的区别来分个类:
-
指令与组件共有的钩子
ngOnChanges
ngOnInit
ngDoCheck
ngOnDestroy
-
组件特有的钩子
ngAfterContentInit
ngAfterContentChecked
ngAfterViewInit
ngAfterViewChecked
Angular 2 指令生命周期钩子的作用及调用顺序
ngOnChanges - 当数据绑定输入属性的值发生变化时调用
ngOnInit - 在第一次 ngOnChanges 后调用
ngDoCheck - 自定义的方法,用于检测和处理值的改变
ngAfterContentInit - 在组件内容初始化之后调用
ngAfterContentChecked - 组件每次检查内容时调用
ngAfterViewInit - 组件相应的视图初始化之后调用
ngAfterViewChecked - 组件每次检查视图时调用
ngOnDestroy - 指令销毁前调用
Angular 2 指令生命周期钩子详解
在详细介绍指令生命周期钩子之前,我们先来介绍一下构造函数:
constructor
组件的构造函数会在所有的生命周期钩子之前被调用,它主要用于依赖注入或执行简单的数据初始化操作。
import { Component, ElementRef } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>Welcome to Angular World</h1>
<p>Hello {{name}}</p>
`,
})
export class AppComponent {
name: string = '';
constructor(public elementRef: ElementRef) { // 使用构造注入的方式注入依赖对象
this.name = 'Semlinker'; // 执行初始化操作
}
}
ngOnChanges
当数据绑定输入属性的值发生变化的时候,Angular 将会主动调用 ngOnChanges 方法。它会获得一个 SimpleChanges 对象,包含绑定属性的新值和旧值,它主要用于监测组件输入属性的变化。
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h4>Welcome to Angular World</h4>
<exe-child name="exe-child-component"></exe-child>
`,
})
export class AppComponent { }
child.component.ts
import { Component, Input, SimpleChanges, OnChanges } from '@angular/core';
@Component({
selector: 'exe-child',
template: `
<p>Child Component</p>
<p>{{ name }}</p>
`
})
export class ChildComponent implements OnChanges{
@Input()
name: string;
ngOnChanges(changes: SimpleChanges) {
console.dir(changes);
}
}
以上代码运行后,浏览器的输出结果:
ngOnInit
在第一次 ngOnChanges 执行之后调用,并且只被调用一次。它主要用于执行组件的其它初始化操作或获取组件输入的属性值。
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'exe-child',
template: `
<p>父组件的名称:{{pname}} </p>
`
})
export class ChildComponent implements OnInit {
@Input()
pname: string; // 父组件的名称
constructor() {
console.log('ChildComponent constructor', this.pname); // Output:undefined
}
ngOnInit() {
console.log('ChildComponent ngOnInit', this.pname); // output: 输入的pname值
}
}
ngOnDestory
在指令被销毁前,将会调用 ngOnDestory 方法。它主要用于执行一些清理操作,比如:移除事件监听、清除定时器、退订 Observable 等。
@Directive({
selector: '[destroyDirective]'
})
export class OnDestroyDirective implements OnDestroy {
sayHello: number;
constructor() {
this.sayHiya = window.setInterval(() => console.log('hello'), 1000);
}
ngOnDestroy() {
window.clearInterval(this.sayHiya);
}
}
ngDoCheck
当组件的输入属性发生变化时,将会触发 ngDoCheck 方法。我们可以使用该方法,自定义我们的检测逻辑。它也可以用来加速我们变化检测的速度。
ngAfterContentInit
在组件使用 ng-content
指令的情况下,Angular 会在将外部内容放到视图后用。它主要用于获取通过 @ContentChild 或 @ContentChildren 属性装饰器查询的内容视图元素。
具体使用示例,请参考 - Angular 2 ContentChild & ContentChildren
ngAfterContentChecked
在组件使用 ng-content
指令的情况下,Angular 会在检测到外部内容的绑定或者每次变化的时候调用。
ngAfterViewInit
在组件相应的视图初始化之后调用,它主要用于获取通过 @ViewChild 或 @ViewChildren 属性装饰器查询的视图元素。
具体使用示例,请参考 - Angular 2 ViewChild & ViewChildren
ngAfterViewChecked
组件每次检查视图时调用
Angular 2 LifecycleHooks 、SimpleChanges 等相关接口
LifecycleHooks 接口
export interface OnChanges { ngOnChanges(changes: SimpleChanges): void; }
export interface OnInit { ngOnInit(): void; }
export interface DoCheck { ngDoCheck(): void; }
export interface OnDestroy { ngOnDestroy(): void; }
export interface AfterContentInit { ngAfterContentInit(): void; }
export interface AfterContentChecked { ngAfterContentChecked(): void; }
export interface AfterViewInit { ngAfterViewInit(): void; }
export interface AfterViewChecked { ngAfterViewChecked(): void; }
SimpleChange
// 用于表示变化对象
export class SimpleChange {
constructor(public previousValue: any,
public currentValue: any,
public firstChange: boolean) {}
// 标识是否为首次变化
isFirstChange(): boolean { return this.firstChange; }
}
SimpleChanges
export interface SimpleChanges { [propName: string]: SimpleChange; }
Angular 2 View 详解
在 Angular 2 中 View (视图) 由三个部分组成:
Elements - 元素
Bindings - 绑定
Events - 事件
在 Angular 2 TemplateRef & ViewContainerRef 这篇文章中,我们介绍了 Angular 2 支持的 View(视图) 类型:
Embedded Views - Template 模板元素
Host Views - Component 组件
接下来我们来分析一下组件对应的 Host Views,具体示例如下:
child.component.ts
import { Component, Input, SimpleChanges, OnChanges, AfterViewChecked } from '@angular/core';
@Component({
selector: 'exe-child',
template: `
<p>Child Component</p>
<p>{{ name }}</p>
`
})
export class ChildComponent implements OnChanges, AfterViewChecked{
@Input()
name: string;
ngOnChanges(changes: SimpleChanges) {
console.dir(changes);
setTimeout(() => {
this.name = 'exe-child-component-1'
}, 0);
}
ngAfterViewChecked() {
console.log('ngAfterViewChecked hook has been called');
}
}
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h4>Welcome to Angular World</h4>
<exe-child name="exe-child-component"></exe-child>
`,
})
export class AppComponent { }
以上代码运行后,浏览器的输出结果:
接下来我们来分析一下 ChildComponent 组件,先来看一下编译后的 component.ngfactory.js 文件。
ChildComponent/component.ngfactory.js 代码片段:
function View_ChildComponent0(viewUtils,parentView,parentIndex,parentElement) {
var self = this;
...
self._expr_7 = jit_CD_INIT_VALUE5;
}
/*
* 用于初始化模板内的元素
* ChildComponent - template
* <p>Child Component</p>
* <p>{{ name }}</p>
*/
View_ChildComponent0.prototype.createInternal = function(rootSelector) {
var self = this;
var parentRenderNode = self.renderer.createViewRoot(self.parentElement);
...
// (1) 创建p元素 - <p>Child Component</p>
self._el_1 = jit_createRenderElement6(self.renderer,parentRenderNode,
'p', jit__object_Object_7,self.debug(1,1,6));
// 创建文本元素,设置内容为 - 'Child Component'
self._text_2 = self.renderer.createText(self._el_1,'Child Component',
self.debug(2,1,9));
// (2) 创建p元素 - <p>{{ name }}</p>
self._el_4 = jit_createRenderElement6(self.renderer,parentRenderNode,
'p', jit__object_Object_7,self.debug(4,2,6));
self._text_5 = self.renderer.createText(self._el_4,'',self.debug(5,2,9));
self.init(null,(self.renderer.directRenderer? null: [...]
),null);
return null;
};
// 执行变化检测
View_ChildComponent0.prototype.detectChangesInternal = function(throwOnChange) {
var self = this;
self.debug(5,2,9);
var currVal_7 = jit_inlineInterpolate8(1,'',self.context.name,'');
if (jit_checkBinding9(throwOnChange,self._expr_7,currVal_7)) {
self.renderer.setText(self._text_5,currVal_7);
self._expr_7 = currVal_7;
}
};
ChildComponent/wrapper.ngfactory.js 代码片段:
function Wrapper_ChildComponent() {
var self = this;
self._changed = false;
self._changes = {}; // 创建Changes对象
self.context = new jit_ChildComponent0();
self._expr_0 = jit_CD_INIT_VALUE1; // {}
}
Wrapper_ChildComponent.prototype.ngOnDestroy = function() { };
Wrapper_ChildComponent.prototype.check_name =function(currValue,
throwOnChange, forceUpdate) {
var self = this;
// 判断值是否更新,jit_checkBinding2中直接使用looseIdentical(oldValue, newValue)
// 进行全等比较(===)
if ((forceUpdate || jit_checkBinding2(throwOnChange,self._expr_0,currValue))) {
self._changed = true;
self.context.name = currValue;
// 创建name关联的SimpleChange对象
self._changes['name'] = new jit_SimpleChange3(self._expr_0,currValue);
self._expr_0 = currValue;
}
};
Wrapper_ChildComponent.prototype.ngDoCheck = function(view,el,throwOnChange) {
var self = this;
var changed = self._changed;
self._changed = false;
if (!throwOnChange) { if (changed) {
self.context.ngOnChanges(self._changes);
jit_setBindingDebugInfoForChanges4(view.renderer,el,self._changes);
self._changes = {};
} }
return changed;
};
...
return Wrapper_ChildComponent
})
我有话说
1.注册指令生命周期钩子时,一定要实现对应的接口么 ?
注册指令生命周期钩子时,实现对应的接口不是必须的,接口可以帮助我们在开发阶段尽早地发现错误,因为我们有可能在注册生命周期钩子的时候,写错了某个钩子的名称,在运行时可能不会抛出任何异常,但页面显示却不是预期的效果,因此建议读者还是遵守该开发规范。另外还要注意的一点是,TypeScript 中定义的接口,是不会编译生成 ES5 相关代码,它只用于编译阶段做校验。
未完待续
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。