ng-repeat finish event

I want to call some jQuery function targeting div with table. That table is populated with ng-repeat.

When I call it on

$(document).ready()

I have no result.

Also

$scope.$on('$viewContentLoaded', myFunc);

doesn't help.

Is there any way to execute function right after ng-repeat population completes? I've read an advice about using custom directive, but I have no clue how to use it with ng-repeat and my div...

Indeed, you should use directives, and there no event tied to the end of a ng-Repeat loop (as each element is constructed individually, and has it's own event). But a) using directives might be all you need and b) there are a few ng-Repeat specific properties you can use to make your "on ngRepeat finished" event.

Specifically, if all you want is to style/add events to the whole of the table, you can do so using in a directive that encompasses all the ngRepeat elements. On the other hand, if you want to address each element specifically, you can use a directive within the ngRepeat, and it will act on each element, after it is created.

Then, there are the $index, $first, $middle and $last properties you can use to trigger events. So for this HTML:

<div ng-controller="Ctrl" my-main-directive>
  <div ng-repeat="thing in things" my-repeat-directive>
    thing {{thing}}
  </div>
</div>

You can use directives like so:

angular.module('myApp', [])
.directive('myRepeatDirective', function() {
  return function(scope, element, attrs) {
    angular.element(element).css('color','blue');
    if (scope.$last){
      window.alert("im the last!");
    }
  };
})
.directive('myMainDirective', function() {
  return function(scope, element, attrs) {
    angular.element(element).css('border','5px solid red');
  };
});

See it in action in this Plunker. Hope it helps!

If you simply want to execute some code at the end of the loop, here's a slightly simpler variation that doesn't require extra event handling:

<div ng-controller="Ctrl">
  <div class="thing" ng-repeat="thing in things" my-post-repeat-directive>
    thing {{thing}}
  </div>
</div>
function Ctrl($scope) {
  $scope.things = [
    'A', 'B', 'C'  
  ];
}

angular.module('myApp', [])
.directive('myPostRepeatDirective', function() {
  return function(scope, element, attrs) {
    if (scope.$last){
      // iteration is complete, do whatever post-processing
      // is necessary
      element.parent().css('border', '1px solid black');
    }
  };
});

See a live demo.

Here is a repeat-done directive that calls a specified function when true. I have found that the called function must use $timeout with interval=0 before doing DOM manipulation, such as initializing tooltips on the rendered elements. jsFiddle: http://jsfiddle.net/tQw6w/

In $scope.layoutDone, try commenting out the $timeout line and uncommenting the "NOT CORRECT!" line to see the difference in the tooltips.

<ul>
    <li ng-repeat="feed in feedList" repeat-done="layoutDone()" ng-cloak>
    <a href="{{feed}}" title="view at {{feed | hostName}}" data-toggle="tooltip">{{feed | strip_http}}</a>
    </li>
</ul>

JS:

angular.module('Repeat_Demo', [])

    .directive('repeatDone', function() {
        return function(scope, element, attrs) {
            if (scope.$last) { // all are rendered
                scope.$eval(attrs.repeatDone);
            }
        }
    })

    .filter('strip_http', function() {
        return function(str) {
            var http = "http://";
            return (str.indexOf(http) == 0) ? str.substr(http.length) : str;
        }
    })

    .filter('hostName', function() {
        return function(str) {
            var urlParser = document.createElement('a');
            urlParser.href = str;
            return urlParser.hostname;
        }
    })

    .controller('AppCtrl', function($scope, $timeout) {

        $scope.feedList = [
            'http://feeds.feedburner.com/TEDTalks_video',
            'http://feeds.nationalgeographic.com/ng/photography/photo-of-the-day/',
            'http://sfbay.craigslist.org/eng/index.rss',
            'http://www.slate.com/blogs/trending.fulltext.all.10.rss',
            'http://feeds.current.com/homepage/en_US.rss',
            'http://feeds.current.com/items/popular.rss',
            'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml'
        ];

        $scope.layoutDone = function() {
            //$('a[data-toggle="tooltip"]').tooltip(); // NOT CORRECT!
            $timeout(function() { $('a[data-toggle="tooltip"]').tooltip(); }, 0); // wait...
        }

    })

Here's a simple approach using ng-init that doesn't even require a custom directive. It's worked well for me in certain scenarios e.g. needing to auto-scroll a div of ng-repeated items to a particular item on page load, so the scrolling function needs to wait until the ng-repeat has finished rendering to the DOM before it can fire.

<div ng-controller="MyCtrl">
    <div ng-repeat="thing in things">
        thing: {{ thing }}
    </div>
    <div ng-init="fireEvent()"></div>
</div>

myModule.controller('MyCtrl', function($scope, $timeout){
    $scope.things = ['A', 'B', 'C'];

    $scope.fireEvent = function(){

        // This will only run after the ng-repeat has rendered its things to the DOM
        $timeout(function(){
            $scope.$broadcast('thingsRendered');
        }, 0);

    };
});

Note that this is only useful for functions you need to call one time after the ng-repeat renders initially. If you need to call a function whenever the ng-repeat contents are updated then you'll have to use one of the other answers on this thread with a custom directive.

Maybe a bit simpler approach with ngInit and Lodash's debounce method without the need of custom directive:

Controller:

$scope.items = [1, 2, 3, 4];

$scope.refresh = _.debounce(function() {
    // Debounce has timeout and prevents multiple calls, so this will be called 
    // once the iteration finishes
    console.log('we are done');
}, 0);

Template:

<ul>
    <li ng-repeat="item in items" ng-init="refresh()">{{item}}</li>
</ul>

Update

There is even simpler pure AngularJS solution using ternary operator:

Template:

<ul>
    <li ng-repeat="item in items" ng-init="$last ? doSomething() : null">{{item}}</li>
</ul>

Be aware that ngInit uses pre-link compilation phase - i.e. the expression is invoked before child directives are processed. This means that still an asynchronous processing might be required.

It may also be necessary when you check the scope.$last variable to wrap your trigger with a setTimeout(someFn, 0). A setTimeout 0 is an accepted technique in javascript and it was imperative for my directive to run correctly.

Complementing Pavel's answer, something more readable and easily understandable would be:

<ul>
    <li ng-repeat="item in items" 
        ng-init="$last ? doSomething() : angular.noop()">{{item}}</li>
</ul>

Why else do you think angular.noop is there in the first place...?

Advantages:

You don't have to write a directive for this...

This is an improvement of the ideas expressed in other answers in order to show how to gain access to the ngRepeat properties ($index, $first, $middle, $last, $even, $odd) when using declarative syntax and isolate scope (Google recommended best practice) with an element-directive. Note the primary difference: scope.$parent.$last.

angular.module('myApp', [])
.directive('myRepeatDirective', function() {
  return {
    restrict: 'E',
    scope: {
      someAttr: '='
    },
    link: function(scope, element, attrs) {
      angular.element(element).css('color','blue');
      if (scope.$parent.$last){
        window.alert("im the last!");
      }
    }
  };
});

If you simply wants to change the class name so it will rendered differently, below code would do the trick.

<div>
<div ng-show="loginsuccess" ng-repeat="i in itemList">
    <div id="{{i.status}}" class="{{i.status}}">
        <div class="listitems">{{i.item}}</div>
        <div class="listitems">{{i.qty}}</div>
        <div class="listitems">{{i.date}}</div>
        <div class="listbutton">
            <button ng-click="UpdateStatus(i.$id)" class="btn"><span>Done</span></button>
            <button ng-click="changeClass()" class="btn"><span>Remove</span></button>
        </div>
    <hr>
</div>

This code worked for me when I had a similar requirement to render the shopped item in my shopping list in Strick trough font.