一、前言
最近做了一個圖片懶加載的小插件,功能需要dom渲染完成后,好獲取那些需要懶加載的dom元素。那么問題來了,如果只是感知靜態的dom用ready,onload都可以,但項目用的angular,ng-repeat什么時候循環完,或者說angular自身的生命周期中dom渲染完成怎么知道,這里做個解決問題的記錄。
二、網上流傳的解決方案
1.data-ng-init---無效
大概意思是,給你需要監聽的dom,比如body添加一個data-ng-init屬性,綁定你需要在body加載完成后執行的方法。
<div data-ng-init = "load()"></div> $scope.load = function () { //dosomething };
我查了下資料,在stackoverflow中找到了相關介紹,data-ng-init本質是ng-init,只是在對於H5之前,ng寫法會報錯,為了解決這個錯誤而添加data前綴達到兼容的寫法,所以本質還是ng-init。
在HTML5開始之后,像Visual Studio這樣的代碼編輯器突出顯示'ng-',這是無效的。但實際上它是有效的,所以有一種方法可以讓代碼編輯器通過在前面加上'data-ng- *'來理解AngularJS的屬性是有效的。
因此,當在任何HTML5代碼編輯器中使用前綴時,它不會強調屬性並將它們視為有效。
這是'data- *'前綴的最初目的。-----點我跳轉原回答
那我們就改為ng-init測試下,當我執行ng-init中的代碼時,是不是連angular自身的動態dom都加載完成了。
我將ng-init直接綁在了一個需要ng-repeat的ul上,當斷點已經執行了我load方法
我去看了此時的dom渲染情況
ul里面一個li都沒有,空的,說明根本沒解析完成啊,這個方法也就能感知下靜態dom渲染,angular的無效,所以不符合我的要求,排除。
2.$viewContentLoaded事件---無效
大量博客都說了這個方法,那看來是非常的有效啊,去官網查了下api,介紹少之又少
英文不好,大概意思是,需要結合ng-view指令使用,只要ng-view指令范圍的視圖需要重新渲染,通過監聽$viewContentLoaded,就能針對改變做你想做的操作了。
<div ng-view></div>
$scope.$on('$viewContentLoaded', function () { //dosomething });
測試了下,代碼沒執行,又去翻了下資料,懷疑是不是自己用錯了,找到了一個關於使用的的特殊說明
當ngView內容被重新加載時,從ngView作用域上發布, 通過$emit將事件沿着作用域向上傳播(子作用域到父作用域),也就是說你監聽這個事件必須得在那個View的上層作用域。----點我查看原文
也沒錯啊,將$on換成$watch還是沒效,先不說有沒有效,這東西只是說感知ng-view變化時執行,沒說dom加載完成后執行,不是我要找的東西。排除在外。
3.自定義指令,$last === true---有bug
因為我做圖片懶加載的要求是,在執行懶加載方法前這些img元素都已經渲染好了,我能抓到它們。而這些圖片說到底就是通過ng-repeat渲染出來的,既然感知angular dom渲染完成無效,換種思路,能不能得知ng-repeat什么時候渲染完成呢?
通過自定義指令repeatFinish,監聽ng-repeat狀態。
<ul> <li ng-repeat="item in data track by $index" repeat-finish></li> </ul> angular .module("mainApp") .directive('repeatFinish', [function () { //判斷ng-repeat是否渲染完成的自定義指令,暫時沒用到,以后可能會用 return { restrict: 'EA', link: function (scope, element, attr) { if(scope.$last === true) { //dosomething }; }, }; }]);
在ng-repeat過程中,scope作用域中有一個$last的狀態變量,當循環到最后一個元素時,它就會變成true,而這個方法是寫在link中的,link是為dom綁定相關指令事件的,趕緊去測試下,打個斷點
出問題了,我需要循環的數組其實有10條數據,理論上來說,一開始索引$index應該從0開始,但是這里卻直接從1開始了,也就是第二條數據,假設我需要循環的數據一共就1條,link里面的函數直接就不觸發了。
其次,因為我實際使用是在產品分類頁中,點擊不同產品分類,被循環的數據data其實是在改變的,有趣的是,假設A類產品有4條,B類產品有3條,由4條切換到3條的過程中,也不會觸發link中的函數。
對於這種做法的問題,大概歸納為兩點:
一是數據只有1條時監聽不到,方法是通用的,誰知道你要遍歷的數據有幾條。
當需要repeat的數據是可變的,由多變少的過程不會觸發,少變多的過程會觸發,說到底還是有問題,用不了,有興趣的同學可以寫demo測試下,我暫時也解釋不了為什么會這樣。
三、靠譜的解決方案
功夫不負有心人,在簡書的一篇文章中,找到了可行靠譜的方法,使用$timeout。
<ul> <li ng-repeat="item in data track by $index"></li> </ul> $timeout(function () { //處理dom加載完成,或者repeat循環完成要做的事情 },0);
原理是什么呢,大家都知道,js的定時器其實也是異步的,$timeout其實只是angular為了能自動觸發臟檢測而封裝的方法,同樣也是異步。將你需要執行的方法放在$timeout中,它就會等到所有的dom渲染完成以及同步邏輯跑完最后執行,真的是讓人眼前一亮!
方案出處 實現AngularJS渲染完畢后執行腳本
四、關於寫博客的自我反應
在我查解決方案的過程中,我確實是被一些博客弄的特別煩躁和惱火,文章內容全靠復制,代碼自己不試驗,比如談到$viewContentLoaded幾乎沒有人提都沒提這個東西是結合ng-view使用的,內容全是大同小異,怎么用也不說清楚,復制粘貼來的東西終究是別人的,那這篇博客的產出說到底浪費自己和讀者的時間。這也提醒了我自己,對於以后的博客編寫,涉及到代碼相關的,一定親自試驗,保證可用。
學習不是一天兩天,沒有捷徑,唯有積累。