В любом случае, чтобы вызвать метод, когда Angular сделано с добавлением обновлений области в DOM?

Я ищу способ выполнить код, когда после добавления изменений в переменную $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/

Спасибо в Advance!

Ответ 1

$evalAsync очередь используется для планирования работы, которая должна произойти за пределами текущего фрейма стека, но перед визуализацией браузера. - http://docs.angularjs.org/guide/concepts#runtime

Хорошо, а что такое "стек кадров"? Комментарий Github показывает больше:

если вы ставите в очередь из контроллера, то он будет раньше, но если вы в очереди из директивы, то это будет после. - https://github.com/angular/angular.js/issues/734#issuecomment-3675158

Выше, Misko обсуждает, когда запускается код, который поставлен в очередь для выполнения $evalAsync, в отношении того, когда DOM обновляется Angular. Я также предлагаю прочитать два комментария Github, чтобы получить полный контекст.

Поэтому, если код поставлен в очередь с использованием $evalAsync из директивы, он должен запускаться после того, как DOM был обработан Angular, но до того, как браузер отобразит. Если вам нужно запустить что-то после отображения браузера или после обновления контроллера модели, используйте $timeout(..., 0);

См. также fooobar.com/questions/18515/..., в котором также есть пример скрипта, который использует $evalAsync().

Ответ 2

Я разветкил твою скрипку. 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']);

Ответ 3

Использование тайм-аута - неправильный способ сделать это. Используйте директиву для добавления/управления DOM. Если вы используете таймаут, обязательно используйте $timeout, который подключен к Angular (например, возвращает обещание).

Ответ 4

Если вы похожи на меня, вы заметите, что во многих случаях $timeout с ожиданием 0 работает задолго до того, как 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
})

Ответ 5

У меня была настраиваемая директива, и мне понадобилось результирующее свойство height() element внутри моей директивы, что означало, что мне нужно было прочитать его после того, как angular выполнил весь $digest, и браузер вытек из макета.

В функции link моей директивы;

Это не работает надежно, но не достаточно поздно;

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

Это было еще недостаточно поздно:

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

Казалось, что следующее работа (даже с 0ms в Chrome), где любопытно даже ẁ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
        });
    });
});

Тогда, конечно, я могу просто использовать a;

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

Ответ 6

работает для меня, например:

interval = $interval(function() {
    if ($("#target").children().length === 0) {
        return;
    }
    doSomething();
    $interval.cancel(interval);
}, 0);