Angular2學(xué)習(xí)教程之ng中變更檢測(cè)問題詳解
開發(fā)中遇到的問題
在開發(fā)中遇到一個(gè)這樣的問題,代碼不便透露,這里用簡(jiǎn)單的例子還原一下問題所在:
有三個(gè)組件,第一個(gè)是用來(lái)展示Todo列表的組件TodoComponent,Todo是個(gè)類,包含id和name屬性。
@Component({
selector: 'todo-list',
template: `
<p *ngFor='let item of todos'>{{ item.name }}</p>
`,
})
export class TodoComponent{
@Input() todos: Todo[];
public getTodos():Todo[]{
return this.todos;
}
}
第二個(gè)組件同樣是一個(gè)Todo列表展示組件TodoDataComponent ,不同的是該組件需要一個(gè)TodoComponent類型的輸入,并從TodoComponent組件中獲得需要展示的Todo數(shù)據(jù)。
@Component({
selector: 'app-todo-data',
template: `<p *ngFor='let item of todos'>{{ item.name }}</p>
<button (click)='getData()'>get data</button>`,
styleUrls: ['./todo-data.component.css'],
inputs: ['todoComponent'],
})
export class TodoDataComponent implements OnInit {
todoComponent: TodoComponent;
todos: Todo[]
constructor() { }
ngOnInit() {
}
getData(){
this.todos=this.todoComponent.getTodos();
}
}
最后一個(gè)是應(yīng)用的根組件,根組件根據(jù)loading值來(lái)確定是否加載TodoComponent組件,并展示TodoDataComponent 組件。
//app.component.htm
<div>
<div *ngIf='loading'>
<todo-list [todos]='todos'></todo-list>
<button (click)='changeall()'>next</button>
</div>
</div>
<div>
<app-todo-data [todoComponent]='todoComponent'></app-todo-data>
</div>
//app.component.ts
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
todos: Todo[];
@ViewChild(TodoComponent)
todoComponent: TodoComponent;
loading: boolean = true;
constructor(private todoService:TodoService){
super(true);
}
ngOnInit(){
this.todoService.todos.subscribe(data => {this.todos=data});
this.todoService.load(0, 3);
}
changeall(){
this.todoService.load(3, 3);
}
}
這樣問題就來(lái)了,TodoComponent 組件是否在頁(yè)面上展示是不確定的,在上面的例子中根組件最開始沒有渲染TodoComponent組件,最后根據(jù)loading的值將TodoComponent渲染出來(lái)。而TodoDataComponent 組件的顯示又需要一個(gè)TodoComponent 進(jìn)行初始化(跟組件通過(guò)@ViewChild(TodoComponent)獲得),這樣造成在開發(fā)模式下出現(xiàn)以下錯(cuò)誤:
template:9:16 caused by: Expression has changed after it was checked. Previous value: 'undefined'. Current value: '[object Object]'.
該錯(cuò)誤僅在開發(fā)模式下會(huì)報(bào)告出來(lái)的,解決掉總是更好的選擇,防止在生產(chǎn)環(huán)境下出現(xiàn)問題。
問題的原因及解決辦法
這個(gè)問題是ng2中的變更檢測(cè)策略造成的,ng2并沒有智能到一有數(shù)據(jù)變更就能自動(dòng)檢測(cè)到的,執(zhí)行變更檢測(cè)的一些情況有:組件中的輸入發(fā)生變化、組件中有事件響應(yīng)、setTimeOut函數(shù)等。
這樣在上面的小例子中, @ViewChild(TodoComponent)todoComponent: TodoComponent;從undefined到[object Object],而并沒有觸發(fā)ng的變更檢測(cè)。
解決辦法也很簡(jiǎn)單,ng支持手動(dòng)觸發(fā)變更檢測(cè),只要在適當(dāng)?shù)奈恢茫{(diào)用變更檢測(cè)即可。
在上面的例子中,解決辦法為:
從@angular/core引入AfterViewInit, ChangeDetectorRef。注入ChangeDetectorRef對(duì)象,并在聲明周期鉤子ngAfterViewInit中調(diào)用變更
constructor(private todoService:TodoService, private cdr: ChangeDetectorRef){}
ngAfterViewInit(){
this.cdr.detectChanges();
}
ChangeDetectorRef
用來(lái)處理ng變更的類,可以使用它來(lái)進(jìn)行完全的手動(dòng)變更檢測(cè),主要有一下方法:
1.markForCheck()標(biāo)記為需要進(jìn)行變更檢測(cè),官方給的一下例子,setInterval不會(huì)觸發(fā)變更檢測(cè),因此模板上的numberOfTicks 并不會(huì)發(fā)生變化。
setInterval(() => {
this.numberOfTicks ++
// the following is required, otherwise the view will not be updated
this.ref.markForCheck();
}, 1000);
2.detach()從變更檢測(cè)樹上分離,即該組件不會(huì)進(jìn)行自動(dòng)的變更檢測(cè),變更需要手動(dòng)進(jìn)行,使用detectChanges函數(shù)。
3.detectChanges()手動(dòng)檢測(cè)變更,當(dāng)變更檢測(cè)代價(jià)較大時(shí),可以設(shè)置為定時(shí)進(jìn)行表更檢測(cè)
ref.detach();
setInterval(() => {
this.ref.detectChanges();
}, 5000);
4.checkNoChanges()進(jìn)行變更檢測(cè),有變更時(shí)拋出異常
5.reattach()與detach()方法的作用相反
其他一些變更檢測(cè)知識(shí)
angular2中的每一個(gè)組件都關(guān)聯(lián)到一個(gè)變更檢測(cè)器,ChangeDetectorRef可以用來(lái)控制變更檢測(cè)器進(jìn)行檢測(cè)。
瀏覽器的以下行為可以出發(fā)檢測(cè)器進(jìn)行檢測(cè):
1.所有瀏覽器事件
2.setTimeout()和setInterval()
3.Ajax請(qǐng)求
OnPush變更檢測(cè)模式
組件默認(rèn)使用的是Default變更檢測(cè)模式,只要組件的輸入發(fā)生變化時(shí),就會(huì)觸發(fā)檢測(cè)器的執(zhí)行。除Default模式外,還有一種OnPush變更檢測(cè)模式,使用該模式首先需要在組件聲明修飾符中添加
@Component({
selector: 'todo-list',
changeDetection: ChangeDetectionStrategy.OnPush,
})
聲明為OnPush變更檢測(cè)模式意味著當(dāng)組件輸入發(fā)生變化時(shí),不一定會(huì)觸發(fā)變更檢測(cè)器,只有當(dāng)該輸入的引用發(fā)生變化時(shí),檢測(cè)器才會(huì)觸發(fā)。例如在一個(gè)數(shù)組中某個(gè)下標(biāo)的值發(fā)生變化時(shí),檢測(cè)器不會(huì)觸發(fā),視圖不會(huì)更新,只有該數(shù)組引用發(fā)生變化時(shí),視圖才會(huì)更新。當(dāng)然瀏覽器事件、observable發(fā)出的事件等還是會(huì)觸發(fā)檢測(cè)器的。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問大家可以留言交流,謝謝大家腳本之家的支持。
相關(guān)文章
Angular2學(xué)習(xí)教程之組件中的DOM操作詳解
這篇文章主要給大家介紹了Angular2學(xué)習(xí)教程之組件中DOM操作的相關(guān)資料,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來(lái)看看吧。2017-05-05
Angular4集成ng2-file-upload的上傳組件
本篇文章主要介紹了Angular4集成ng2-file-upload的上傳組件,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
Angularjs實(shí)現(xiàn)數(shù)組隨機(jī)排序的方法
今天小編就為大家分享一篇Angularjs實(shí)現(xiàn)數(shù)組隨機(jī)排序的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-10-10
Angular懶加載機(jī)制刷新后無(wú)法回退的快速解決方法
使用oclazyload懶加載angular的模塊,刷新頁(yè)面后,單擊回退按鈕無(wú)法返回上一個(gè)頁(yè)面.怎么回事呢?下面小編給大家?guī)?lái)了angular懶加載機(jī)制刷新后無(wú)法回退的快速解決方法,非常不錯(cuò),感興趣的朋友參考下2016-08-08
Angular中使用ng-zorro圖標(biāo)庫(kù)部分圖標(biāo)不能正常顯示問題
這篇文章主要介紹了Angular中使用ng-zorro圖標(biāo)庫(kù)部分圖標(biāo)不能正常顯示問題,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04
使用Angular內(nèi)置模塊進(jìn)行HTTP請(qǐng)求
這篇文章主要介紹了使用Angular內(nèi)置模塊進(jìn)行HTTP請(qǐng)求方法步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
Angular 1.x個(gè)人使用的經(jīng)驗(yàn)小結(jié)
這篇文章主要給大家介紹了關(guān)于Angular 1.x個(gè)人使用的一些經(jīng)驗(yàn),屬于一些基礎(chǔ)入門教程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-07-07

