1. 程式人生 > >angular監聽dom渲染完成,判斷ng-repeat迴圈完成

angular監聽dom渲染完成,判斷ng-repeat迴圈完成

一、前言

最近做了一個圖片懶載入的小外掛,功能需要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使用的,內容全是大同小異,怎麼用也不說清楚,複製貼上來的東西終究是別人的,那這篇部落格的產出說到底浪費自己和讀者的時間。這也提醒了我自己,對於以後的部落格編寫,涉及到程式碼相關的,一定親自試驗,保證可用。

學習不是一天兩天,沒有捷徑,唯有積累。