1. 程式人生 > >angular學習(變化檢測)

angular學習(變化檢測)

angular學習(OnChanges)

1.OnChanges

當元件的任何輸入屬性發生變化,元件生命週期提供的鉤子ngOnChanges 可捕獲變化的內容。

示例:

Parent元件是Child元件的父元件,變化檢測從根元件開始,會比 Child更早執行變化檢測,在執行變化檢測時 Parent中的pa屬性,會傳遞到 Child的輸入屬性param中。此時 Child元件檢測到param屬性發生變化,因此元件內的 p 元素內的文字值從空字串 變成 param的值。

child.component.ts

import { Component, OnInit, Input, OnChanges, SimpleChange } from '@angular/core'
; @Component({ selector: 'exe-child', // templateUrl: './child.component.html', template: ` <p>{{ param1 }}</p> <p>{{ param2 }}</p> <button (click)="changeParam1()">change param1</button> `, styleUrls: ['./child.component.css'] }) export class ChildComponent
implements OnInit {
@Input() param1: string; @Input() param2: string; ngOnChanges(changes: SimpleChange){ console.log(changes); } //不會觸發ngOnChanges鉤子,但能改變param1的值 changeParam1(){ this.param1 = 'abc'; console.log(this.param1); } constructor() { } ngOnInit() { } }

parent.component.ts

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

@Component({
  selector: 'exe-parent',
  // templateUrl: './parent.component.html',
  template: `
    <exe-child [param1]="pa1" [param2]="pa2"></exe-child>
  `,
  styleUrls: ['./parent.component.css']
})
export class ParentComponent implements OnInit {

  pa1: string = 'aaa';
  pa2: string = 'bbb';

  constructor() { }

  ngOnInit() {
    // this.pa1 = '666';
  }

}

用ngOnChanges()捕獲的變化內容如圖:

這裡寫圖片描述

ps:在元件內手動改變輸入屬性的值,ngOnChanges 鉤子不會觸發,
點選change param1 只能改變param1的值,不能觸發ngOnChanges()。

2.變化檢測策略

OnPush策略(當使用OnPush策略的時候,若輸入屬性沒有變化,元件的變化檢測將被跳過)
示例:
profile-card.component.ts

import { Component, OnInit, Input, ChangeDetectionStrategy, SimpleChange } from '@angular/core';

@Component({
  selector: 'profile-card',
  // templateUrl: './profile-card.component.html',
  template: `
      <profile-name [name]='profile.name'></profile-name>
      <profile-age [age]='profile.age'></profile-age>
      <div>astring:{{astring}}</div>
      <button (click)="changeinchild()" >在子元件改變輸入屬性</button>
      <button (click)="changeinchildstring()" >在子元件改變輸入屬性(字串)</button>
  `,
  styleUrls: ['./profile-card.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,//onPush策略
})
export class ProfileCardComponent implements OnInit {

  @Input()
  profile: { name: string, age: number };//可變物件

  @Input()
  astring: string;
  constructor() { 
  }

  ngOnChanges(changes: SimpleChange){
    console.log('觸發ngOnChanges');
    console.log(changes);
  }

  ngOnInit() {
  }

  changeinchild() {
    this.profile.name = '在子元件改變輸入屬性';
    console.log(this.profile);
  }

  changeinchildstring() {
    this.astring = '在子元件改變輸入屬性(字串)';
    console.log(this.astring);
  }
}

testfive.component.ts

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

@Component({
  selector: 'ons-page[testfive]',
  // templateUrl: './testfive.component.html',
  template: `
    <ons-page>
      <ons-toolbar>
        <div class="left"><ons-back-button></ons-back-button></div>
      </ons-toolbar>
      <profile-card [profile]='profile' [astring]='astring'></profile-card>
      <br/>
      <button (click)="changeinparent()">在父元件改變輸入屬性</button>
      <button (click)="changeinparentstring()">在父元件改變輸入屬性(字串)</button>
    </ons-page>
  `,
  styleUrls: ['./testfive.component.css']
})
export class TestfiveComponent implements OnInit {

  profile : { name: string, age: number } = {
    name: 'ashin',//輸入屬性變化
    age: 3//輸入屬性變化
  };

  astring : string = 'astring';

  constructor() {
  }

  ngOnInit() {

  }

  changeinparent(){
    this.profile.name = "在父元件改變輸入屬性";
    console.log(this.profile);
  }

  changeinparentstring(){
    this.astring = "在父元件改變輸入屬性(字串)";
    console.log(this.astring);
  }
}
3.OnChanges觸發時機、用預設策略和OnPush策略的不同
  1. 輸入屬性是可變物件
- 沒用OnPush 用了OnPush
在父元件改變子元件的輸入屬性 沒觸發,頁面上的值改變【圖a】【888】 沒觸發,頁面上的值沒改變【圖b】【999】
在子元件改變輸入屬性 沒觸發,頁面上的值改變【圖c】【666】 沒觸發,頁面上的值改變【圖d】【666】

2. 輸入屬性是字串(不可變物件)

- 沒用OnPush 用了OnPush
在父元件改變子元件的輸入屬性 觸發,頁面上的值改變【圖e】【777】 觸發,頁面上的值改變【圖f】【777】
在子元件改變輸入屬性 沒觸發,頁面上的值改變【圖g】【666】 沒觸發,頁面上的值改變【h】【666】

圖a:
這裡寫圖片描述
這裡寫圖片描述
圖b:
這裡寫圖片描述
圖c:
這裡寫圖片描述
圖d:
這裡寫圖片描述

圖e:
這裡寫圖片描述
這裡寫圖片描述
圖f:
這裡寫圖片描述
圖g:
這裡寫圖片描述
圖h:
這裡寫圖片描述

綜上:

在子元件內手動改變輸入屬性,不會觸發ngOnChanges鉤子【666】

在父元件內手動改變輸入屬性時

  1. 輸入屬性是不可變物件時會觸發ngOnChanges鉤子【777】
  2. 輸入屬性是可變物件
    1. 用預設策略時,子元件的輸入屬性沒有發生變化(可變物件內的引用發生變化時才是發生變化,值發生變化不是發生變化),但會從根元件到子元件開始執行變化檢測,所以值會在子元件變化檢測時改變【888】
    2. 用OnPush策略時,子元件的輸入屬性沒有發生變化,也就不會執行檢測,值不會跟著變化【999】

參考:
Angular系列之變化檢測(Change Detection)
Angular 2 Change Detection - 2
onChanges鉤子使用

4. ChangeDetectorRef

變化檢測類,是元件的變化檢測器的引用

ChangeDetectorRef 變化檢測類的主要方法:

  • markForCheck() - 在元件的 metadata 中如果設定了 changeDetection:ChangeDetectionStrategy.OnPush 條件,那麼變化檢測不會再次執行,除非手動呼叫該方法, 該方法的意思是在變化監測時必須檢測該元件。
  • detach() - 從變化檢測樹中分離變化檢測器,該元件的變化檢測器將不再執行變化檢測,除非手動呼叫 reattach() 方法。
  • reattach() - 重新新增已分離的變化檢測器,使得該元件及其子元件都能執行變化檢測
  • detectChanges() - 從該元件到各個子元件執行一次變化檢測

    1. markForCheck()
import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, Input, SimpleChange } from '@angular/core';

@Component({
  selector: 'sixchild',
  template: `
    <p>sixchild</p>
    <p>當前值:{{ counter }}</p>
    1.markForCheck()
  `,
  //1. OnPush前,定時器setInterval()內的counter值會同步的檢視上
  //2. OnPush時,元件不會執行變化檢測,可呼叫markForCheck()執行檢測
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SixchildComponent implements OnInit {
  counter: number=0 ;

  constructor(private cdRef: ChangeDetectorRef) { }

  ngOnInit() {
    setInterval(()=>{
      this.counter++;
      //執行檢測
      this.cdRef.markForCheck();
      console.log(this.counter);
    },1000)
  }
}

這裡寫圖片描述

2.detach()/reattach();

關閉/開啟變化檢測,和是否用OnPush()無關。

import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, Input, SimpleChange } from '@angular/core';

@Component({
  selector: 'childsix',
  template: `
    <p>childsix</p>
    <br/>
    <p>當前值:{{ counter }}</p>
    2.detach()/reattach()
    <br/>
    <p>開啟/關閉<input type="checkbox" (click)="changetach($event.target.checked)"/></p>

  `,
  // changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ChildsixComponent implements OnInit {
  counter: number=0 ;

  constructor(private cdRef: ChangeDetectorRef) { }

  ngOnInit() {
    setInterval(()=>{
      this.counter++;
      console.log(this.counter);
    },1000)
  }

  //開啟/關閉變化檢測
  changetach(checked: boolean) {
    if(checked) {
      console.log('開啟變化檢測');
      this.cdRef.reattach();
    }else {
      console.log('關閉變化檢測');
      this.cdRef.detach();
    }
  }
}


  1. detectChanges()

從該元件到各個子元件執行一次變化檢測
在父元件testsixComponent中新增OnPush策略,那麼子元件也不會有變化檢測,子元件sixchild和childsix(子元件內沒有新增OnPush)內的setInterval()裡更新的資料沒有更新到檢視。
此時在父元件內呼叫detectChanges(),則會從該元件到各個子元件執行變化檢測(不知道這樣理解對不對?)
這裡寫圖片描述

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

@Component({
  selector: 'ons-page[testsix]',
  template: `
    <ons-page>
      <ons-toolbar>
        <div class="left"><ons-back-button></ons-back-button></div> 
        <div class="center">testsix</div>
      </ons-toolbar>
      <sixchild></sixchild>
      <br/>
      <br/>
      <br/>
      <childsix></childsix>
      <br/>
      <br/>
      <br/>
      <p>six:{{six}}</p>
    </ons-page>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TestsixComponent implements OnInit {
  six: number = 0;

  constructor(private cdRef: ChangeDetectorRef) { }

  ngOnInit() {
    setInterval(()=>{
      //從該元件到各個子元件執行一次變化檢測
      this.six++;
      this.cdRef.detectChanges();
    },1000)
  }
}