当Angular完成向DOM添加范围更新时如何触发方法?

问题描述 投票:36回答:6

在我将更改添加到$ scope变量之后,我正在寻找一种执行代码的方法,在本例中为$ scope.results。我需要这样做,以便在它可以执行之前调用一些遗留代码,这些遗留代码要求项目在DOM中。

我的真实代码是触发AJAX调用,并更新范围变量以更新ui。所以我目前我的代码在推送到示波器后立即执行,但遗留代码失败,因为dom元素尚未可用。

我可以使用setTimeout()添加一个丑陋的延迟,但这并不能保证DOM真正准备就绪。

我的问题是,有什么方法可以绑定到“渲染”类似的事件?

var myApp = angular.module('myApp', []);

myApp.controller("myController", ['$scope', function($scope){
    var resultsToLoad = [{id: 1, name: "one"},{id: 2, name: "two"},{id: 3, name: "three"}];
    $scope.results = [];

    $scope.loadResults = function(){
        for(var i=0; i < resultsToLoad.length; i++){
            $scope.results.push(resultsToLoad[i]);
        }
    }

    function doneAddingToDom(){
        // do something awesome like trigger a service call to log
    }
}]);
angular.bootstrap(document, ['myApp']);

链接到模拟代码:http://jsfiddle.net/acolchado/BhApF/5/

提前致谢!

angularjs angularjs-scope
6个回答
61
投票

$evalAsync队列用于调度需要在当前堆栈帧之外但在浏览器的视图渲染之前发生的工作。 - http://docs.angularjs.org/guide/concepts#runtime

好的,那么什么是“堆栈框架”? Github评论显示更多:

如果你从控制器入队,那么它将在之前,但如果你从指令入队,那么它将在之后。 - https://github.com/angular/angular.js/issues/734#issuecomment-3675158

上面,Misko正在讨论何时运行由$ evalAsync排队执行的代码,与Angular更新DOM的时间有关。我建议之前阅读两个Github评论,以获得完整的上下文。

因此,如果代码使用指令中的$ evalAsync进行排队,则它应该在Angular操纵DOM之后但在浏览器呈现之前运行。如果您需要在浏览器渲染后运行某些内容,或者在控制器更新模型后运行,请使用$timeout(..., 0);

另请参阅https://stackoverflow.com/a/13619324/215945,它也有一个使用$ evalAsync()的示例小提琴。


5
投票

我分叉你的小提琴。 http://jsfiddle.net/xGCmp/7/

我添加了一个名为emit-when的指令。它需要两个参数。要发出的事件以及要发出的事件必须满足的条件。这是有效的,因为当在指令中执行链接函数时,我们知道该元素已在DOM中呈现。我的解决方案是在渲染ng-repeat中的最后一项时发出一个事件。

如果我们有一个全Angular解决方案,我不建议这样做。这有点hacky。但是,它可能是处理您提到的遗留代码类型的一种解决方案。

var myApp = angular.module('myApp', []);

myApp.controller("myController", ['$scope', function($scope){
    var resultsToLoad = [
        {id: 1, name: "one"},
        {id: 2, name: "two"},
        {id: 3, name: "three"}
    ];

    function doneAddingToDom() {
        console.log(document.getElementById('renderedList').children.length);
    }

    $scope.results = [];

    $scope.loadResults = function(){
        $scope.results = resultsToLoad;
        // If run doneAddingToDom here, we will find 0 list elements in the DOM. Check console.
        doneAddingToDom();
    }

    // If we run on doneAddingToDom here, we will find 3 list elements in the DOM.
    $scope.$on('allRendered', doneAddingToDom);
}]);

myApp.directive("emitWhen", function(){
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            var params = scope.$eval(attrs.emitWhen),
                event = params.event,
                condition = params.condition;
            if(condition){
                scope.$emit(event);
            }
        }
    }
});

angular.bootstrap(document, ['myApp']);

0
投票

使用超时不是正确的方法。使用指令添加/操作DOM。如果你确实使用超时,请确保使用挂钩到Angular的$ timeout(例如返回一个promise)。


0
投票

如果你像我一样,你会注意到在许多情况下等待0的$ timeout在DOM真正稳定并且完全静止之前运行良好。当我想让DOM保持稳定时,我希望它能稳定gosh dang it。因此,我遇到的解决方案是在元素上设置一个观察者(或者在整个文档下面的示例中),为“DOMSubtreeModified”事件设置一个观察者。一旦我等了500毫秒并且没有更改DOM,我就播放了一个像“domRendered”这样的事件。

IE:

   //todo: Inject $rootScope and $window, 


   //Every call to $window.setTimeout will use this function
   var broadcast = function () {};

   if (document.addEventListener) {

       document.addEventListener("DOMSubtreeModified", function (e) {
           //If less than 500 milliseconds have passed, the previous broadcast will be cleared. 
           clearTimeout(broadcast)
           broadcast = $window.setTimeout(function () {
               //This will only fire after 500 ms have passed with no changes
               $rootScope.$broadcast('domRendered')
           }, 500)

       });

   //IE stupidity
   } else {
       document.attachEvent("DOMSubtreeModified", function (e) {

           clearTimeout(broadcast)
           broadcast = $window.setTimeout(function () {
               $rootScope.$broadcast('domRendered')
           }, 500)

       });
   }

像所有广播一样,这个事件可以像这样被挂钩:

$rootScope.$on("domRendered", function(){
   //do something
})

0
投票

我有一个自定义指令,我需要在我的指令中生成height()element属性,这意味着我需要在角度运行整个$digest之后阅读它并且浏览器已经流出了布局。

在我的指令的link函数中;

这不能可靠地工作,不够晚;

scope.$watch(function() {}); 

这还不够晚;

scope.$evalAsync(function() {});

以下似乎工作(即使在Chrome上0ms)奇怪地甚至ẁindow.setTimeout()scope.$apply()没有;

$timeout(function() {}, 0);

闪烁是一个问题,所以最后我使用requestAnimationFrame()回退到我的指令内的$timeout(适当的适当的供应商前缀)。简化,这基本上看起来像;

scope.$watch("someBoundPropertyIexpectWillAlterLayout", function(n,o) {
    $window.requestAnimationFrame(function() {
        scope.$apply(function() {
            scope.height = element.height(); // OK, this seems to be accurate for the layout
        });
    });
});

那当然我可以用一个;

scope.$watch("height", function() {
    // Adjust view model based on new layout metrics
});

0
投票

间隔对我有用,例如:

interval = $interval(function() {
    if ($("#target").children().length === 0) {
        return;
    }
    doSomething();
    $interval.cancel(interval);
}, 0);
© www.soinside.com 2019 - 2024. All rights reserved.