Запустить код jQuery после того, как AngularJS завершает рендеринг HTML

В контроллере я получаю некоторые данные JSON, используя $http или $resource services. Затем я пишу эти данные в $scope и AngularJS обновляет структуру HTML страницы. Моя проблема в том, что мне нужно знать, что такое новый размер (ширина и высота) списка (я имею в виду элемент HTML DOM), который заполняется директивой Angular ng-repeat. Следовательно, я должен запустить javascript-код сразу после завершения Angular, чтобы обновить структуру DOM. Каков правильный способ сделать это? Я искал интернет за последние четыре часа, но я не мог найти решение моей проблемы.

Вот как я получаю данные JSON:

var tradesInfo = TradesInfo.get({}, function(data){
    console.log(data);
    $scope.source.profile = data.profile;
            $scope.trades = $scope.source.profile.trades;
        $scope.activetrade = $scope.trades[0];
        $scope.ready = true;


    init();  //I need to call this function after update is complete

});

И вот что происходит в init():

function init(){
    alert($('#wrapper').width());
    alert($('#wrapper').height());
}

Я знаю, что для решения этой проблемы должно быть что-то легкое, но я не могу просто найти ее сейчас. Спасибо заранее.

Ответ 1

На самом деле в этом случае способ angular - это не простой способ, но единственный правильный путь:)

Вам нужно написать директиву и прикрепить элемент, который вы хотите узнать о высоте. А из контроллера вы передаете событие, директива поймает событие и там вы сможете выполнять манипуляции с DOM. НИКОГДА в контроллере.

var tradesInfo = TradesInfo.get({}, function(data){
    console.log(data);
    $scope.source.profile = data.profile;
    ...

    $scope.$broadcast('dataloaded');
});


directive('heightStuff', ['$timeout', function ($timeout) {
    return {
        link: function ($scope, element, attrs) {
            $scope.$on('dataloaded', function () {
                $timeout(function () { // You might need this timeout to be sure its run after DOM render.
                    element.width()
                    element.height()
                }, 0, false);
            })
        }
    };
}]);

Ответ 2

Ответ Olivér хорош, но имеет проблему: если вы забудете трансляцию события, ваш javascript не будет работать, тогда как ваши данные могут быть изменены. Другим решением было бы наблюдать за изменениями в области, например:

var tradesInfo = TradesInfo.get({}, function(data) {
  console.log(data);
  $scope.profile = data.profile;
  // ...
});


directive('heightStuff', ['$timeout',
  function($timeout) {
    return {
      scope: {
        myData: '='
      },
      link: function($scope, element, attrs) {
        $scope.$watch('myData', function() {
          $timeout(function() { // You might need this timeout to be sure its run after DOM render.
            element.width()
            element.height()
          }, 0, false);
        })
      }
    };
  }
]);
<div height-stuff my-data="profile"></div>

Таким образом, функции javascript вызывается каждый раз, когда данные изменяются без каких-либо особых событий.

Ответ 3

Другое предложение работать с JQuery. Если бы это было сделано для сетки, которая была создана в директиве. Я хотел прокрутить до определенной строки в сетке. Используйте $emit для передачи из директивы родительскому контроллеру:

В контроллере:

    ['$timeout',function($timeout){
...
 $scope.$on('dataloaded', function () {
            $timeout(function () { // You might need this timeout to be sure its run after DOM render.
                $scope.scrollToPosition();
            }, 0, false);
        });
        $scope.scrollToPosition = function () {
            var rowpos = $('#row_' + $scope.selectedActionID, "#runGrid").position();
            var tablepost = $('table', "#runGrid").position();
            $('#runGrid').scrollTop(rowpos.top - tablepost.top);
        }

В директиве

.directive('runGrid',['$timeout', function ($timeout) {
        // This directive generates the grip of data
        return {
            restrict: 'E',  //DOM Element
            scope: {    //define isolated scope
                list: '=',   //use the parent object
                selected: "="
            },

            templateUrl: '/CampaignFlow/StaticContent/Runs/run.grid.0.0.0.0.htm',  //HTML template URL

            controller: ['$scope', function ($scope) {  //the directive private controller, whith its private scope
                //$scope.statusList = [{ data_1: 11, data_2: 12 }, { data_1: 21, data_2: 22 }, { data_1: 31, data_2: 32 }];
                //Controller contains sort functionallity

                $scope.sort = { column: null, direction: 1 }
                $scope.column = null;
                $scope.direction = "asc";
                $scope.sortColumn = function (id) {
                    if(id!=$scope.column) {
                        $scope.column = id;
                        $scope.direction = "asc";
                    } else {
                        $scope.column = null;
                    }
                }
                $scope.toggleDir = function () {
                    $scope.direction = ($scope.direction == "asc") ? "desc" : "asc";
                }
               $scope.$emit('dataloaded');
            }]


        };
    }])

И это фрагмент шаблона html директивы grid:

 <div style="overflow-y:auto;height: 200px;" id="runGrid">
            <table class="table table-striped" style="table-layout:fixed">
           <tbody>
                <tr  ng-repeat="status in list" id="row_{{status.action_id}}" ng-class="(status.action_id==selected)?'selected':''">
                    <td>

список и выбранные параметры вводятся из html, который использует директиву

<run-grid list="list" selected="selectedActionID"></run-grid>

Ответ 4

Я хотел бы добавить еще один ответ, так как в предыдущих ответах он понимает, что код, необходимый для запуска после выполнения ngRepeat, представляет собой код angular, который в этом случае все ответы выше дают большое и простое решение, некоторые более общий, чем другие, и в случае его важного этапа жизненного цикла дайджеста вы можете взглянуть на блог Ben Nadel об этом, за исключением использования $parse вместо $eval.

Но по моему опыту, как утверждает OP, в нем обычно запускаются некоторые плагины или методы jQuery на компилируемом DOM, который в этом случае я обнаружил, что самым простым решением является создание директивы с setTimeout, так как функция setTimeout попадает в конец очереди браузера, ее всегда справа, когда все делается в angular, обычно ng-repeat, которая продолжается после того, как родительская функция postLinking

angular.module('myApp', [])
.directive('pluginNameOrWhatever', function() {
  return function(scope, element, attrs) {        
    setTimeout(function doWork(){
      //jquery code and plugins
    }, 0);        
  };
});

Для тех, кто задается вопросом, почему не использовать $timeout, его то, что он вызывает другой цикл дайджеста, совершенно не нужно.

EDIT:

Thanx to drzaus для ссылки на способ использования $timeout без вызывания дайджест http://www.codelord.net/2015/10/14/angular-nitpicking-differences-between-timeout-and-settimeout/