在介紹 Angular 2 Directive Lifecycle (生命週期) 以前,咱們先來介紹一下 Angular 2 中 Directive (指令) 與 Component (組件) 的關係。typescript
咱們再來看一下 Angular 2 中定義的指令和組件接口:segmentfault
// 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 中全部的鉤子以下圖所示:angular2
怎麼那麼多鉤子,是否是被嚇到了,沒事咱們基於指令與組件的區別來分個類:app
指令與組件共有的鉤子ide
ngOnChanges函數
ngOnInit動畫
ngDoCheckthis
ngOnDestroyspa
組件特有的鉤子
ngAfterContentInit
ngAfterContentChecked
ngAfterViewInit
ngAfterViewChecked
ngOnChanges - 當數據綁定輸入屬性的值發生變化時調用
ngOnInit - 在第一次 ngOnChanges 後調用
ngDoCheck - 自定義的方法,用於檢測和處理值的改變
ngAfterContentInit - 在組件內容初始化以後調用
ngAfterContentChecked - 組件每次檢查內容時調用
ngAfterViewInit - 組件相應的視圖初始化以後調用
ngAfterViewChecked - 組件每次檢查視圖時調用
ngOnDestroy - 指令銷燬前調用
在詳細介紹指令生命週期鉤子以前,咱們先來介紹一下構造函數:
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
組件每次檢查視圖時調用
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 (視圖) 由三個部分組成:
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 相關代碼,它只用於編譯階段作校驗。
未完待續