Run jQuery code after AngularJS finishes rendering HTML

In the controller, I get some JSON data using $ http or $ resource services. Then I write this data in $ scope and AngularJS updates the structure of the HTML page. My problem is that I need to know what the new size (width and height) of the list (I mean the HTML DOM element) is, which is populated with the Angular ng-repeat directive. Therefore, I have to run javascript code right after Angular completes, in order to update the DOM structure. What is the right way to do this? I searched the internet for the past four hours, but I could not find a solution to my problem.

This is how I get JSON data:

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

And this is what happens in init() :

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

I know that there must be something easy to solve this problem, but I canโ€™t just find it now. Thanks in advance.

+67
javascript jquery angularjs
Jun 05 '13 at 9:04 on
source share
4 answers

Actually in this case, the angular method is not an easy way, but the only right way :)

You need to write a directive and attach the element that you want to know about the height. And from the controller you pass the event, the directive will catch the event and there you can perform manipulations with the DOM. NEVER in the controller.

 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); }) } }; }]); 
+62
Jun 05 '13 at 9:25
source share

Olivรฉr's answer is good, but it has a problem: if you forget to broadcast the event, your javascript will not work, while your data may be changed. Another solution would be to monitor changes in the area, for example:

 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> 

This way, javascript functions are called every time the data changes without any special events.

+6
Aug 12 '15 at 10:26
source share

Another suggestion is to work with jQuery. If this were done for the grid that was created in the directive. I wanted to scroll to a specific line in the grid. Use $ emit to pass from the directive to the parent controller:

In the controller:

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

In the directive

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

And this is a fragment of the html template of the grid directive:

  <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> 

the list and selected parameters are entered from html, which uses the directive

 <run-grid list="list" selected="selectedActionID"></run-grid> 
+5
Apr 01 '14 at 11:34
source share

I would like to add another answer, as in previous answers he understands that the code needed to run after ngRepeat is executed is angular code, which in this case, all the answers above give a big and simple solution, some more general than others , and in the case of its important phase of the digest life cycle, you can take a look at Ben Nadel's blog about this, except for using $ parse instead of $ eval.

But in my experience, according to the OP, it usually runs some plugins or jQuery methods on a compiled DOM, which in this case I found that the simplest solution is to create a directive with setTimeout , since the setTimeout function gets to the end of the browser queue, it always right when everything is done in angular, usually ng-repeat , which continues after the parent function postLinking

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

For those who are wondering why not using $ timeout, it is completely unnecessary that it calls another digest cycle.

EDIT:

Thanx to drzaus for a link to the way to use $ timeout without calling a digest http://www.codelord.net/2015/10/14/angular-nitpicking-differences-between-timeout-and-settimeout/

+3
Dec 30 '15 at 15:31
source share



All Articles