Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to trigger a method when Angular is done adding scope updates to the DOM?

I am looking for a way to execute code when after I add changes to a $scope variable, in this case $scope.results. I need to do this in order to call some legacy code that requires the items to be in the DOM before it can execute.

My real code is triggering an AJAX call, and updating a scope variable in order to update the ui. So I currently my code is executing immediately after I push to the scope, but the legacy code is failing because the dom elements are not available yet.

I could add an ugly delay with setTimeout(), but that doesn't guarantee that the DOM is truly ready.

My question is, is there any ways I can bind to a "rendered" like event?

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

Link to simulated code: http://jsfiddle.net/acolchado/BhApF/5/

Thanks in Advance!

like image 694
acolchado Avatar asked Jun 20 '13 22:06

acolchado


2 Answers

The $evalAsync queue is used to schedule work which needs to occur outside of current stack frame, but before the browser's view render. -- http://docs.angularjs.org/guide/concepts#runtime

Okay, so what's a "stack frame"? A Github comment reveals more:

if you enqueue from a controller then it will be before, but if you enqueue from directive then it will be after. -- https://github.com/angular/angular.js/issues/734#issuecomment-3675158

Above, Misko is discussing when code that is queued for execution by $evalAsync is run, in relation to when the DOM is updated by Angular. I suggest reading the two Github comments before as well, to get the full context.

So if code is queued using $evalAsync from a directive, it should run after the DOM has been manipulated by Angular, but before the browser renders. If you need to run something after the browser renders, or after a controller updates a model, use $timeout(..., 0);

See also https://stackoverflow.com/a/13619324/215945, which also has an example fiddle that uses $evalAsync().

like image 106
Mark Rajcok Avatar answered Oct 09 '22 09:10

Mark Rajcok


I forked your fiddle. http://jsfiddle.net/xGCmp/7/

I added a directive called emit-when. It takes two parameters. The event to be emitted and the condition that has to be met for the event to be emitted. This works because when the link function is executed in the directive, we know that the element has been rendered in the DOM. My solution is to emit an event when the last item in the ng-repeat has been rendered.

If we had an all Angular solution, I would not recommend doing this. It is kind of hacky. But, it might be an okey solution for handling the type of legacy code that you mention.

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']); 
like image 32
Benny Johansson Avatar answered Oct 09 '22 11:10

Benny Johansson