AngularJS中DOM渲染完成的时机

127 阅读2分钟

在进行一个老项目的开发中,引入的jQuery插件需要DOM操作,因为页面包裹在异步加载的ng-include中,尝试以下三种方式来保证DOM渲染完成

  • $timeout

    // $timeout接收三个参数,分别为要执行的函数,延迟(默认为0),是否要执行一次$digest循环(默认为true)
    $timeout(function () {
        // 在这里执行DOM加载完成后的操作
    },0,true);
    
  • 事件监听

    // 在onload时发送一个事件
    <div ng-include="'/path/to/template.html'" onload="templateLoaded()" template-loaded></div>
    // 在controller中广播事件
    app.controller('MyController', function($scope) {
        $scope.templateLoaded = function() {
            // 挂载时广播事件  
            $scope.$broadcast('TemplateLoadedEvent');
        };
    });
    // 在directive中监听事件
    app.directive('templateLoaded', function() {
        return {
            restrict: 'A',
            link: function(scope, element, attrs) {
                scope.$on('TemplateLoadedEvent', function() {
                    // 在这里执行DOM加载完成后的操作
                });
            }
        };
    });
    
  • $includeContentLoaded

    // $includeContentLoaded会在ng-include指令加载、编译并插入模板内容到 DOM 后触发
    $scope.$on('$includeContentLoaded', function () {
        // 在这里执行DOM加载完成后的操作
    });
    

上面几种方法仅对部分DOM元素有效,但均不能保证在ng-repeat内部的元素挂载完成后进行操作。因为ng-repeatng-include都是异步执行的,执行过程中可能会跨越多个$digest循环,而$timeout只确保在当前 $digest 循环之后、下一个事件循环之前执行。 ​ 因此$timeout内部的函数执行时,ng-include 的 AJAX 请求可能还没有返回,ng-repeat 中的列表项也可能未处理完成。同样,使用$evalAsync$applyAsync也不能保证在ng-repeat内部的元素挂载完成后进行操作。 ​ 即使给$timeout加上延迟,也不是一个可靠的办法,因为延迟的时间难以估计,会受浏览器性能、客户端机器的性能、网络延迟等多方面的影响。

后面两种方法也只对ng-include有效,如果想保证ng-repeat也能够加载完成,可以使用 ng-repeat 提供的$last ,它是一个布尔值,用来指示当前迭代的元素是否是数组中的最后一个元素。代码如下

  • 使用自定义指令

    // ng-repeat最后一个元素渲染完毕后,发送事件
    app.directive('RepeatFinished',function () {
        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                if (scope.$last === true) {
                    //确保当前$digest循环结束
                    $timeout(function () {
                        // 发送事件
                        scope.$emit('onRepeatFinished');
                    });
                }
            },
        }; 
    });
    // 在对应位置绑定指令
    <div ng-repeat="item in items" on-finish-render></div>
    // 在controller中监听事件
    app.controller('MyController', function($scope) {
        $scope.$on('ngRepeatFinished', function() {
            // 在这里执行DOM加载完成后的操作
        });
    });
    
  • ng-init

    //绑定ng-init对应的处理函数
    <div ng-repeat="item in items" ng-init="initItem($last)"></div>
    // 在controller中处理
    app.controller('MyController', function($scope) {
        $scope.initItem = function (isLast) {
            if (isLast) {
                //确保当前$digest循环结束
                $timeout(function () {
                    // 在这里执行DOM加载完成后的操作
                });
            }
        };
    });
    

总结:尽量避免在AngularJS中进行DOM操作,对于相关的操作要小心处理

参考链接:github.com/angular/ang…