最近做公司新項目用的angular7,中碰到了一個很頭疼的問題在綁定對象中的數據改變時,頁面視圖沒有跟新,需點擊頁面中的時間元素後才會更新。以前使用angularJs也經常碰到類似情況,這種時候一般的方式使使用臟檢查(Dirty checking)讓angularJs檢查綁定到視圖上的數據來實現對頁 ...
最近做公司新項目用的angular7,中碰到了一個很頭疼的問題在綁定對象中的數據改變時,頁面視圖沒有跟新,需點擊頁面中的時間元素後才會更新。以前使用angularJs也經常碰到類似情況,這種時候一般的方式使使用臟檢查(Dirty checking)讓angularJs檢查綁定到視圖上的數據來實現對頁面數據的刷新。
接觸angular7時間還不久,angular高版本中提供了一系列組件的生命周期鉤子,能在組件生命周期的各個階段,對組件進行操作,監測組件中輸入數據的變化,基本少很少碰到需要用到類似於angularJs中臟檢查的情況。一直以為在高版本中的angular中已經不會出現需要臟檢查的情況了,可是該來的遲早會來。
在項目中由於使用了公司的自己的播放插件,angular自身的事件註冊機制無效,只能使用原生的js位視頻播放插件註冊點擊事件。實現點擊插件後通過插件綁定的回調函數將該分屏信息發送給父組件,在父組件中通過對父組件綁定對象中的屬性值的改變來改變父組件的樣式。
// 插件中的代碼片段
ngAfterViewInit() {
// 頁面視圖初始化完成之後為視頻播放插件添加點擊事件
this.videoObj = document.getElementById('obj' + this.screen.id);
this.videoObj.addEventListener('click', () => {
this.selectThisScreen(this.screen);
}, false);
}
private selectThisScreen(screen) {
// 輸出該分屏對象,輸出給父組件
this.screenSelected.emit(screen);
}
// 父組件中的代碼片段
private selectedScreen(screen) {
this.videoLayoutService.selectedScreen(screen, this.videoLayoutOutput);
}
// videoLayoutService 片段
this.screensConfig.screens.map(item => {
if (item.id === screen.id) {
item.selected = !item.selected; // 這是想要更新的值!!
if (item.selected) {
videoLayoutOutput.emit({
device: {
id: screen.device.Id,
code: screen.device.SerialNo,
selectedCamera: screen.device.SelectedCamera
}
});
} else {
videoLayoutOutput.emit({ device: { id: null, code: null, selectedCamera: null } });
}
} else if (item.id !== screen.id) {
item.selected = false;
}
});
可是由於插件事件綁定只支持原生js的方式,沒有有使用angular自身的事件綁定機制(已測試,在不適用該插件的條件下,使用angular自身的事件綁定機制view層能夠監測到數據的跟新),view層沒能監測到綁定數據的變化。在嘗試了各種方式,和各種生命周期鉤子後(甚至測試了ngDoCheck)都沒有效果後都沒能達到想要的效果,這時我想會不會現在angular7中也存在和angularJs中數據更新視圖沒更新的一樣問題,如果是那也應該有類似與$scope.$applay()這樣類似的方式來解決這個問題。
然後去查了一下官網文檔,在文檔中沒有直接找到類似方法。後去網上找,後來在篇文章中發現了NgZone這個服務(具體哪一篇忘記記錄了,找的博客太多了後來忘記了),然後去官網查了查用法。
官網文檔中使這樣描述的:An injectable service for executing work inside or outside of the Angular zone.(用於在Angular區域內外執行工作的可註入服務。)看的雲里霧裡。。然後又看下麵的說明。
The most common use of this service is to optimize performance when starting a work consisting of one or more asynchronous tasks that don't require UI updates or error handling to be handled by Angular. Such tasks can be kicked off via runOutsideAngular and if needed, these tasks can reenter the Angular zone via run.
大概是說這個服務最常見的用途是在啟動一個或多個非同步組成工作是優化性能時使用,這些任務不由angular處理UI更新或錯誤處理(感動終於找到問題所在了!因為插件使用原生js進行事件綁定,沒有走angular自身的事件綁定機制導致了angular框架不處理UI更新。。所以需要在這裡使用NgZone這個服務),這些任務可以通過runOutsideAngular啟動,如果需要,這些任務可以通過run重新進入Angular區域。
也就是說我現在可以使用NgZone服務的提供的run()方法將這個事件加入到angular的UI更新管理區域中,一切引刃而解。
在父組件中註冊NgZone服務使用run()方法
// 父組件中的代碼片段
constructor(private videoLayoutService: VideoLayoutService, private zone: NgZone) {
}
private selectedScreen(screen) {
this.zone.run(() => {
this.videoLayoutService.selectedScreen(screen, this.videoLayoutOutput);
});
}
最後來簡單說明下NgZone中的幾個方法的使用
run():在Angular區域內同步執行fn函數,並返回函數返回的值。通過run運行函數允許您從在Angular區域之外執行的任務(通常通過runOutsideAngular啟動)重新進入Angular區域。在這個函數中調度的任何未來任務或微任務都將在Angular區域中繼續執行。如果發生同步錯誤,它將被重新拋出,而不是通過onError報告。
runTask():以任務的形式在Angular區域內同步執行fn函數,並返回函數返回的值。通過run運行函數允許您從在Angular區域之外執行的任務(通常通過runOutsideAngular啟動)重新進入Angular區域。在這個函數中調度的任何未來任務或微任務都將在Angular區域中繼續執行。如果發生同步錯誤,它將被重新拋出,而不是通過onError報告。
runGuarded() :與 run() 相同,除了同步錯誤是通過onError捕獲和轉發的,而不是重新拋出。
runOutsideAngular() : 在Angular的父區同步執行fn函數,並返回函數返回的值。通過runOutsideAngular運行函數可以讓您逃離Angular的區域,並做一些不會觸發Angular變化檢測或受制於Angular錯誤處理的工作。在這個函數中調度的任何未來任務或微任務都將在Angular區域之外繼續執行。使用run重新進入Angular區域並執行更新應用程式模型的工作。