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!
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().
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']);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With