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策略的不同
- 輸入屬性是可變物件
- | 沒用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】
在父元件內手動改變輸入屬性時
- 輸入屬性是不可變物件時會觸發ngOnChanges鉤子【777】
- 輸入屬性是可變物件
- 用預設策略時,子元件的輸入屬性沒有發生變化(可變物件內的引用發生變化時才是發生變化,值發生變化不是發生變化),但會從根元件到子元件開始執行變化檢測,所以值會在子元件變化檢測時改變【888】
- 用OnPush策略時,子元件的輸入屬性沒有發生變化,也就不會執行檢測,值不會跟著變化【999】
參考:
Angular系列之變化檢測(Change Detection)
Angular 2 Change Detection - 2
onChanges鉤子使用
4. ChangeDetectorRef
變化檢測類,是元件的變化檢測器的引用
ChangeDetectorRef 變化檢測類的主要方法:
- markForCheck() - 在元件的 metadata 中如果設定了 changeDetection:ChangeDetectionStrategy.OnPush 條件,那麼變化檢測不會再次執行,除非手動呼叫該方法, 該方法的意思是在變化監測時必須檢測該元件。
- detach() - 從變化檢測樹中分離變化檢測器,該元件的變化檢測器將不再執行變化檢測,除非手動呼叫 reattach() 方法。
- reattach() - 重新新增已分離的變化檢測器,使得該元件及其子元件都能執行變化檢測
detectChanges() - 從該元件到各個子元件執行一次變化檢測
- 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();
}
}
}
- 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)
}
}