What I am trying to implement is basically a "on ng repeat finished rendering" handler. I am able to detect when it is done but I can't figure out how to trigger a function from it.
Check the fiddle:http://jsfiddle.net/paulocoelho/BsMqq/3/
JS
var module = angular.module('testApp', [])
.directive('onFinishRender', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
element.ready(function () {
console.log("calling:"+attr.onFinishRender);
// CALL TEST HERE!
});
}
}
}
});
function myC($scope) {
$scope.ta = [1, 2, 3, 4, 5, 6];
function test() {
console.log("test executed");
}
}
HTML
<div ng-app="testApp" ng-controller="myC">
<p ng-repeat="t in ta" on-finish-render="test()">{{t}}</p>
</div>
Answer: Working fiddle from finishingmove: http://jsfiddle.net/paulocoelho/BsMqq/4/
Notice that I didn't use
.ready()
but rather wrapped it in a$timeout
.$timeout
makes sure it's executed when the ng-repeated elements have REALLY finished rendering (because the$timeout
will execute at the end of the current digest cycle -- and it will also call$apply
internally, unlikesetTimeout
). So after theng-repeat
has finished, we use$emit
to emit an event to outer scopes (sibling and parent scopes).And then in your controller, you can catch it with
$on
:With html that looks something like this:
Very easy, this is how I did it.
The answers that have been given so far will only work the first time that the
ng-repeat
gets rendered, but if you have a dynamicng-repeat
, meaning that you are going to be adding/deleting/filtering items, and you need to be notified every time that theng-repeat
gets rendered, those solutions won't work for you.So, if you need to be notified EVERY TIME that the
ng-repeat
gets re-rendered and not just the first time, I've found a way to do that, it's quite 'hacky', but it will work fine if you know what you are doing. Use this$filter
in yourng-repeat
before you use any other$filter
:This will
$emit
an event calledngRepeatFinished
every time that theng-repeat
gets rendered.How to use it:
The
ngRepeatFinish
filter needs to be applied directly to anArray
or anObject
defined in your$scope
, you can apply other filters after.How NOT to use it:
Do not apply other filters first and then apply the
ngRepeatFinish
filter.When should I use this?
If you want to apply certain css styles into the DOM after the list has finished rendering, because you need to have into account the new dimensions of the DOM elements that have been re-rendered by the
ng-repeat
. (BTW: those kind of operations should be done inside a directive)What NOT TO DO in the function that handles the
ngRepeatFinished
event:Do not perform a
$scope.$apply
in that function or you will put Angular in an endless loop that Angular won't be able to detect.Do not use it for making changes in the
$scope
properties, because those changes won't be reflected in your view until the next$digest
loop, and since you can't perform an$scope.$apply
they won't be of any use."But filters are not meant to be used like that!!"
No, they are not, this is a hack, if you don't like it don't use it. If you know a better way to accomplish the same thing please let me know it.
Summarizing
I'm very surprised not to see the most simple solution among the answers to this question. What you want to do is add an
ngInit
directive on your repeated element (the element with thengRepeat
directive) checking for$last
(a special variable set in scope byngRepeat
which indicates that the repeated element is the last in the list). If$last
is true, we're rendering the last element and we can call the function we want.The complete code for your HTML markup would be:
You don't need any extra JS code in your app besides the scope function you want to call (in this case,
test
) sincengInit
is provided by Angular.js. Just make sure to have yourtest
function in the scope so that it can be accessed from the template:Use $evalAsync if you want your callback (i.e., test()) to be executed after the DOM is constructed, but before the browser renders. This will prevent flicker -- ref.
Fiddle.
If you really want to call your callback after rendering, use $timeout:
I prefer $eval instead of an event. With an event, we need to know the name of the event and add code to our controller for that event. With $eval, there is less coupling between the controller and the directive.
If you need to call different functions for different ng-repeats on the same controller you can try something like this:
The directive:
In your controller, catch events with $on:
In your template with multiple ng-repeat