37

在我将更改添加到 $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/

提前致谢!

4

6 回答 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() 的示例小提琴。

于 2013-06-21T15:33:19.960 回答
5

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

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

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

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']);
于 2013-06-21T17:27:52.657 回答
0

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

于 2013-06-21T01:47:56.337 回答
0

如果您像我一样,您会注意到在许多情况下等待为 0 的 $timeout 在 DOM 真正稳定和完全静态之前运行得很好。当我希望 DOM 稳定时,我希望它稳定,天哪。所以我遇到的解决方案是为“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
})
于 2013-12-19T03:50:45.593 回答
0

我有一个自定义指令,我需要指令内部的结果height()属性,element这意味着我需要在 angular 运行整个$digest并且浏览器流出布局后读取它。

link我的指令的功能中;

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

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

这还不够晚。

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

以下似乎有效(即使在 Chrome 上使用 0ms),但奇怪的是ẁindow.setTimeout(),即使scope.$apply()没有;

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

闪烁是一个问题,所以最后我求助于在我的指令内部使用requestAnimationFrame()with fallback $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
        });
    });
});

那么我当然可以使用 a;

scope.$watch("height", function() {
    // Adjust view model based on new layout metrics
});
于 2014-11-27T16:40:42.580 回答
0

间隔对我有用,例如:

interval = $interval(function() {
    if ($("#target").children().length === 0) {
        return;
    }
    doSomething();
    $interval.cancel(interval);
}, 0);
于 2015-01-21T15:48:50.033 回答