Execute o código jQuery depois que o AngularJS concluir a renderização de HTML

No controller eu recebo alguns dados JSON usando $ http ou $ resource services. Então escrevo esses dados no $ escopo e o AngularJS atualiza a estrutura HTML da página. Meu problema é que eu preciso saber qual é o novo tamanho (largura e altura) da lista (quero dizer, elemento HTML DOM) que é preenchido com a diretiva Angular ng-repeat . Conseqüentemente, eu tenho que executar o código javascript logo após Angular terminar de atualizar a estrutura DOM. Qual é a maneira correta de fazer isso? Eu pesquisei na internet nas últimas quatro horas, mas não consegui encontrar nenhuma solução para o meu problema.

É assim que recebo dados 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 }); 

E isso é o que acontece na function init() :

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

Eu sei que deve haver algo fácil para resolver esse problema, mas não posso encontrá-lo agora. Desde já, obrigado.

Na verdade, neste caso, o caminho angular não é o caminho mais fácil, mas o único caminho certo 🙂

Você tem que escrever uma diretiva e append ao elemento que você quer saber a altura de. E a partir do controlador você difunde um evento, a diretiva pega o evento e lá você pode fazer a manipulação do DOM. NUNCA no controlador.

 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); }) } }; }]); 

A resposta de Olivér é boa, mas tem um problema: se você esquecer de transmitir o evento, seu javascript não será executado enquanto seus dados podem ter sido alterados. Outra solução seria observar mudanças no escopo, por exemplo:

 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); }) } }; } ]); 
 

Dessa forma, as funções javascript são chamadas toda vez que os dados são alterados sem necessidade de um evento personalizado.

Outra sugestão para trabalhar com o JQuery. Teve que trabalhar isso para uma grade que foi gerada em uma diretiva. Eu queria rolar para uma linha específica na grade. Use $ emit para transmitir da diretiva para o controlador pai:

No controlador:

  ['$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); } 

Na diretriz

 .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'); }] }; }]) 

E este é um trecho do modelo html da diretiva grid:

  

a lista e os parâmetros selecionados são injetados a partir do html que está usando a diretiva

  

Eu gostaria de acrescentar outra resposta, já que as respostas anteriores afirmam que o código necessário para rodar depois que o ngRepeat é feito é um código angular, que nesse caso todas as respostas acima dão uma solução ótima e simples, algumas mais genéricas que outras, e caso seja importante o estágio do ciclo de vida digestivo, você pode dar uma olhada no blog de Ben Nadel sobre isso, com a exceção de usar $ parse em vez de $ eval.

Mas na minha experiência, como o OP declara, é geralmente rodando alguns plugins jQuery ou methods no DOM finalmente compilado, que nesse caso eu achei que a solução mais simples é criar uma diretiva com um setTimeout , já que a function setTimeout fica empurrado para o final da fila do navegador, é sempre logo depois tudo é feito em angular, geralmente ng-repeat que continua depois de seus pais postLinking function

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

Para quem quer que esteja se perguntando por que não usar $ timeout, é porque isso faz com que outro ciclo de digitação seja completamente desnecessário.

EDITAR:

Obrigado a drzaus pelo link de como usar $ timeout sem causar problemas http://www.codelord.net/2015/10/14/angular-nitpicking-differences-between-timeout-and-settimeout/