Angular 我的深究(四)【生命週期鉤子】

Angular 我的深究(四)【生命週期鉤子】

定義:

每一個組件都有一個被 Angular 管理的生命週期。javascript

Angular 建立它,渲染它,建立並渲染它的子組件,在它被綁定的屬性發生變化時檢查它,並在它從 DOM 中被移除前銷燬它。css

Angular 提供了生命週期鉤子,把這些關鍵生命時刻暴露出來,賦予你在它們發生時採起行動的能力。html

除了那些組件內容和視圖相關的鉤子外,指令有相同生命週期鉤子。java

概覽:

 ngOnChanges()  

當 Angular(從新)設置數據綁定輸入屬性時響應。 該方法接受當前和上一屬性值的 SimpleChanges 對象app

當被綁定的輸入屬性的值發生變化時調用,首次調用必定會發生在 ngOnInit() 以前。函數

 ngOnInit()  

在 Angular 第一次顯示數據綁定和設置指令/組件的輸入屬性以後,初始化指令/組件。this

在第一輪 ngOnChanges() 完成以後調用,只調用一次。spa

 ngDoCheck()  

檢測,並在發生 Angular 沒法或不肯意本身檢測的變化時做出反應。3d

在每一個 Angular 變動檢測週期中調用,ngOnChanges() 和 ngOnInit()以後。code

 ngAfterContentInit()  

當把內容投影進組件以後調用。

第一次 ngDoCheck() 以後調用,只調用一次。

 ngAfterContentChecked()  

每次完成被投影組件內容的變動檢測以後調用。

ngAfterContentInit() 和每次 ngDoCheck() 以後調用

 ngAfterViewInit()  

初始化完組件視圖及其子視圖以後調用。

第一次 ngAfterContentChecked() 以後調用,只調用一次。

 ngAfterViewChecked()  

每次作完組件視圖和子視圖的變動檢測以後調用。

ngAfterViewInit() 和每次 ngAfterContentChecked() 以後調用。

 ngOnDestroy()  

當 Angular 每次銷燬指令/組件以前調用並清掃。 在這兒反訂閱可觀察對象和分離事件處理器,以防內存泄漏。

在 Angular 銷燬指令/組件以前調用。

範例:

  ngOnChanges()、ngInit

test2.component.ts

// test2.component.ts:

import { Component, OnInit,Input } from '@angular/core'; @Component({ selector: 'app-test2', templateUrl: './test2.component.html', styleUrls: ['./test2.component.css'] })
//須要繼承 OnInit、OnChanges 接口 export class Test2Component implements OnInit OnChanges{
// 兩個input 來自 父組件 test1 @Input() test2_value1: string; @Input() test2_value2: string; constructor() { } ngOnInit() {
          //判斷 ngOnInit的執行順序 console.log("這裏執行ngOnInit"); } ngOnChanges(changes:SimpleChanges){ console.log(changes); for (let propName in changes) { let chng = changes[propName]; let cur = JSON.stringify(chng.currentValue); let prev = JSON.stringify(chng.previousValue); console.log(`${propName}: 新值 = ${cur}, 舊值 = ${prev}`); } } }

 test1.component.html

<!-- test1.component.html -->
<p>
  test1 works!
</p>
<label> test1 value</label>
<input  type="text" [(ngModel)]="test1_value1" >
<input  type="text" [(ngModel)]="test1_value2" >
<!-- 將test1_value1的值給test2_value1... -->
<app-test2 [test2_value1]="test1_value1" [test2_value2]="test1_value2"></app-test2>

結果:

 

說明:

  • 剛刷新頁面時,將test2_value一、test2_value2的值 從 undefined 變成 一、2。
  • 頁面更改test1_value1,將test2_value1的值  從  1 變成  11。
  • 以此類推, 可使用 ngOnChanges對值發生變化時進行處理。
  • ngInit 的執行順序在 ngOnChanges以後,能夠作一下初始化的工做

 

  ngDoCheck()

test2.component.ts

import { Component, OnInit,Input,OnChanges,DoCheck} from '@angular/core';
@Component({
  selector: 'app-test2',
  templateUrl: './test2.component.html',
  styleUrls: ['./test2.component.css']
})
//須要實現 DoCheck 接口 export class Test2Component implements OnInit OnChanges DoCheck{ @Input() test2_value1: string; @Input() test2_value2: string; constructor() { } ngOnInit() { //console.log("這裏執行ngOnInit"); } ngOnChanges(changes:SimpleChanges){ //console.log(changes); for (let propName in changes) { let chng = changes[propName]; let cur = JSON.stringify(chng.currentValue); let prev = JSON.stringify(chng.previousValue); //console.log(`${propName}: 新值 = ${cur}, 舊值 = ${prev}`); } } ngDoCheck(){ console.log("執行ngDoCheck"); } }  

結果:

說明:

  • 刷新頁面的時候,執行了兩次。
  • 每次鼠標放到,input框上就會執行一次
  • 更改input值,也會執行一次,開銷很是大。慎用!

  ngAfterContentInit()

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  //templateUrl: './app.component.html',
  //在app主組價中,將test2組件放到test1組件中,
  template: `<app-test1>
    <app-test2></app-test2>
    </app-test1>`,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';
}

test1.component.ts

import { Component, OnInit,OnChanges} from '@angular/core';

@Component({
  selector: 'app-test1',
  //templateUrl: './test1.component.html',
  //ng-content指定的是外來的組件 在組件app中定義的 test2組件
  //一樣在test1組件中,也增長test2 組件 
  template: `<div> <ng-content></ng-content> <app-test2></app-test2> </div>`,
  styleUrls: ['./test1.component.css']
})
export class Test1Component implements OnInit, OnChanges{
    test1_value1:string;
    test1_value2:string;
  constructor() { }

  ngOnInit() {
        this.test1_value1="1"
        this.test1_value2="2"
  }
    ngOnChanges(){
        console.log("onchange");
    }
}

test2.component.ts

import { Component, OnInit,Input ,OnChanges,DoCheck,SimpleChanges,AfterContentInit} from '@angular/core';
@Component({
  selector: 'app-test2',
  templateUrl: './test2.component.html',
  styleUrls: ['./test2.component.css']
})
export class Test2Component implements OnInit,OnChanges,DoCheck,AfterContentInit{
    @Input() test2_value1: string;
    @Input() test2_value2: string;
    constructor() { }

    ngOnInit() {
        //console.log("這裏執行ngOnInit");
    }
    ngOnChanges(changes:SimpleChanges){
        //console.log(changes);
        for (let propName in changes) {
            let chng = changes[propName];
            let cur  = JSON.stringify(chng.currentValue);
            let prev = JSON.stringify(chng.previousValue);
            //console.log(`${propName}: 新值 = ${cur}, 舊值 = ${prev}`);
        }
    }
    ngDoCheck(){
    console.log("執行ngDoCheck");
  }
    ngAfterContentInit(){
        console.log("執行ngAfterContentInit");
    }
}

 

結果:

說明:

  • ngAfterContentInit會在外來內容被投影到組件中以後 調用,也就是說當test2組件以html的形式投影到test1組件以後執行,
  • 由於使用兩種方式進行投影了兩次,因此ngAfterContentInit執行了兩次
  • 其餘操做只會增長ngDoCheck的次數,並無增長ngAfterContentInit的次數

 

  ngAfterContentCheck()

 test2.component.ts

import { Component, OnInit,Input ,OnChanges,DoCheck,SimpleChanges,AfterContentInit,AfterContentCheck} from '@angular/core';
@Component({
  selector: 'app-test2',
  templateUrl: './test2.component.html',
  styleUrls: ['./test2.component.css']
})
export class Test2Component implements OnInit,OnChanges,DoCheck,AfterContentInit,AfterContentCheck{
    @Input() test2_value1: string;
    @Input() test2_value2: string;
    constructor() { }

    ngOnInit() {
        //console.log("這裏執行ngOnInit");
    }
    ngOnChanges(changes:SimpleChanges){
        //console.log(changes);
        for (let propName in changes) {
            let chng = changes[propName];
            let cur  = JSON.stringify(chng.currentValue);
            let prev = JSON.stringify(chng.previousValue);
            //console.log(`${propName}: 新值 = ${cur}, 舊值 = ${prev}`);
        }
    }
    ngDoCheck(){
    console.log("執行ngDoCheck");
  }
    ngAfterContentInit(){
        console.log("執行ngAfterContentInit");
    }
    ngAfterContentChecked(){
        console.log("執行ngAfterContentChecked");
    }
}

結果:

說明:

  • 在執行ngDoCheck以後 必定會執行一次ngAfterContentInit
  • 每次完成被投影組件內容的變動檢測以後調用
  • 其餘代碼沒貼出來,就是跟上一個是同樣的

  ngAfterViewInit()

 test1.component.ts

import { Component, OnInit,OnChanges,ViewChild} from '@angular/core';
import {Test2Component} from "../test2/test2.component"

@Component({
  selector: 'app-test1',
  //templateUrl: './test1.component.html',
  template: `<div>  <input  type="text" [(ngModel)]="test1_value1" >
<input  type="text" [(ngModel)]="test1_value2" ><ng-content></ng-content>
<app-test2 [test2_value1]="test1_value1" [test2_value2]="test1_value2"></app-test2> </div>`,
  styleUrls: ['./test1.component.css']
})
export class Test1Component implements OnInit, OnChanges{
    test1_value1:string;
    test1_value2:string;
  constructor() { }
    @ViewChild(Test2Component);
  ngOnInit() {
        this.test1_value1="1"
        this.test1_value2="2"
  }
    ngOnChanges(){
        console.log("onchange");
    }
}

test2.component.ts

import { Component, OnInit,Input ,OnChanges,DoCheck,SimpleChanges,AfterContentInit,AfterContentCheck, AfterViewChecked, AfterViewInit} from '@angular/core';
@Component({
  selector: 'app-test2',
  templateUrl: './test2.component.html',
  styleUrls: ['./test2.component.css']
})
export class Test2Component implements OnInit,OnChanges,DoCheck,AfterContentInit,AfterContentCheck ,AfterViewChecked, AfterViewInit{
    @Input() test2_value1: string;
    @Input() test2_value2: string;
    constructor() { }

    ngOnInit() {
        //console.log("這裏執行ngOnInit");
    }
    ngOnChanges(changes:SimpleChanges){
        //console.log(changes);
        for (let propName in changes) {
            let chng = changes[propName];
            let cur  = JSON.stringify(chng.currentValue);
            let prev = JSON.stringify(chng.previousValue);
            //console.log(`${propName}: 新值 = ${cur}, 舊值 = ${prev}`);
        }
    }
    ngDoCheck(){
    console.log("執行ngDoCheck");
  }
    ngAfterContentInit(){
        console.log("執行ngAfterContentInit");
    }
    ngAfterContentChecked(){
        console.log("執行ngAfterContentChecked");
    }
    ngAfterViewInit(){
        console.log("執行ngAfterViewInit");
    }
}

結果:

說明:

  • 在每次建立了組件的子視圖後調用,每次在test1組件中建立test2組件時都會調用,
  • 在test1組件中,須要使用@ChildView 裝飾器,將test2component裝飾一下

  ngAfterViewChecked

 test2.component.ts

import { Component, OnInit,Input ,OnChanges,DoCheck,SimpleChanges,AfterContentInit,AfterContentCheck, AfterViewChecked, AfterViewInit} from '@angular/core';
@Component({
  selector: 'app-test2',
  templateUrl: './test2.component.html',
  styleUrls: ['./test2.component.css']
})
export class Test2Component implements OnInit,OnChanges,DoCheck,AfterContentInit,AfterContentCheck ,AfterViewChecked, AfterViewInit{
    @Input() test2_value1: string;
    @Input() test2_value2: string;
    constructor() { }

    ngOnInit() {
        //console.log("這裏執行ngOnInit");
    }
    ngOnChanges(changes:SimpleChanges){
        //console.log(changes);
        for (let propName in changes) {
            let chng = changes[propName];
            let cur  = JSON.stringify(chng.currentValue);
            let prev = JSON.stringify(chng.previousValue);
            //console.log(`${propName}: 新值 = ${cur}, 舊值 = ${prev}`);
        }
    }
    ngDoCheck(){
    console.log("執行ngDoCheck");
  }
    ngAfterContentInit(){
        console.log("執行ngAfterContentInit");
    }
    ngAfterContentChecked(){
        console.log("執行ngAfterContentChecked");
    }
    ngAfterViewInit(){
        console.log("執行ngAfterViewInit");
    }
    ngAfterViewChecked(){
        console.log("執行ngAfterViewChecked");
    }
}

結果:

說明:

  • 上圖紅色框是,頁面刷新完後執行的ngAfterViewChecked,前兩次是伴隨init一塊兒的,後面兩個是,test1給test2賦值致使的
  • 上圖綠色框是,更改test1中的一個值,致使了更改了test2的值執行的ngAfterViewChecked
  • 我再test1組件的兩個input上,鼠標焦點來回切換時,一樣也會執行ngAfterViewChecked,仍是慎用這個鉤子函數吧

  OnDestroy

test1.component.ts

import { Component, OnInit,OnChanges,ViewChild} from '@angular/core';
import {Test2Component} from "../test2/test2.component"

@Component({
  selector: 'app-test1',
  //templateUrl: './test1.component.html',
  template: `
        <div>  
            <input  type="text" [(ngModel)]="test1_value1" >
            <input  type="text" [(ngModel)]="test1_value2" >
            <ng-content></ng-content>
            <app-test2 [test2_value1]="test1_value1" [test2_value2]="test1_value2">
            </app-test2> 
              <div *ngFor="let test of tests" appTest4 class="tests">
            {{test}}
        </div>
            <input type="button"value="add" (click)="addDiv()">
            <input type="button"value="delete"(click)="deleteDiv()">
        </div>`,
  styleUrls: ['./test1.component.css']
})
export class Test1Component implements OnInit, OnChanges{
    test1_value1:string;
    test1_value2:string;
    tests:any;
  constructor() { }
    @ViewChild(Test2Component) viewChild:Test2Component;
  ngOnInit() {
        this.test1_value1="1"
        this.test1_value2="2"
        this.tests=[1,2,3]
  }
    ngOnChanges(){
        console.log("onchange");
    }
    addDiv(){
        this.tests.push("1212");
    }
    deleteDiv(){
        this.tests=[];
    }
}

test4.directive.ts

import { Directive, OnInit, OnDestroy  } from '@angular/core';

@Directive({
  selector: '[appTest4]'
})
export class Test4Directive implements OnInit, OnDestroy{

  constructor() { }
  ngOnInit()    { console.log("test4 directive ngOnInit")}

  ngOnDestroy() { console.log("test4 directive ngDestroy");}
}

結果:

說明:

  • 創建一個directive來 監測test1 組件中的 div的生成與銷燬
  • 開始有默認的三個值,全部ngOnInit執行了三次
  • 添加一個值,又執行一次ngOnInit
  • 刪除全部的值,執行了4次ngDestroy

總結:

  因爲生命週期的存在,angular提供了衆多的生命週期的鉤子,讓咱們可以很好的在發生變化的時候進行處理。