[javascript] 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...

The answer is


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.


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.


<div ng-repeat="i in items">
        <label>{{i.Name}}</label>            
        <div ng-if="$last" ng-init="ngRepeatFinished()"></div>            
</div>

My solution was to add a div to call a function if the item was the last in a repeat.


I found an answer here well practiced, but it was still necessary to add a delay

Create the following directive:

angular.module('MyApp').directive('emitLastRepeaterElement', function() {
return function(scope) {
    if (scope.$last){
        scope.$emit('LastRepeaterElement');
    }
}; });

Add it to your repeater as an attribute, like this:

<div ng-repeat="item in items" emit-last-repeater-element></div>

According to Radu,:

$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {mycode});

For me it works, but I still need to add a setTimeout

$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {
setTimeout(function() { 
    mycode
}, 100); });

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.


I had to render formulas using MathJax after ng-repeat ends, none of the above answers solved my problem, so I made like below. It's not a nice solution, but worked for me...

<div ng-repeat="formula in controller.formulas">
    <div>{{formula.string}}</div>
    {{$last ? controller.render_formulas() : ""}}
</div>

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.


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

    })

i would like to add another answer, since the preceding answers takes it that the code needed to run after the ngRepeat is done is an angular code, which in that case all answers above give a great and simple solution, some more generic than others, and in case its important the digest life cycle stage you can take a look at Ben Nadel's blog about it, with the exception of using $parse instead of $eval.

but in my experience, as the OP states, its usually running some JQuery plugins or methods on the finnaly compiled DOM, which in that case i found that the most simple solution is to create a directive with a setTimeout, since the setTimeout function gets pushed to the end of the queue of the browser, its always right after everything is done in angular, usually ngReapet which continues after its parents postLinking function

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

for whoever wondering that in that case why not to use $timeout, its that it causes another digest cycle that is completely unnecessary


I did it this way.

Create the directive

function finRepeat() {
    return function(scope, element, attrs) {
        if (scope.$last){
            // Here is where already executes the jquery
            $(document).ready(function(){
                $('.materialboxed').materialbox();
                $('.tooltipped').tooltip({delay: 50});
            });
        }
    }
}

angular
    .module("app")
    .directive("finRepeat", finRepeat);

After you add it on the label where this ng-repeat

<ul>
    <li ng-repeat="(key, value) in data" fin-repeat> {{ value }} </li>
</ul>

And ready with that will be run at the end of the ng-repeat.


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


There is no need of creating a directive especially just to have a ng-repeat complete event.

ng-init does the magic for you.

  <div ng-repeat="thing in things" ng-init="$last && finished()">

the $last makes sure, that finished only gets fired, when the last element has been rendered to the DOM.

Do not forget to create $scope.finished event.

Happy Coding!!

EDIT: 23 Oct 2016

In case you also want to call the finished function when there is no item in the array then you may use the following workaround

<div style="display:none" ng-init="things.length < 1 && finished()"></div>
//or
<div ng-if="things.length > 0" ng-init="finished()"></div>

Just add the above line on the top of the ng-repeat element. It will check if the array is not having any value and call the function accordingly.

E.g.

<div ng-if="things.length > 0" ng-init="finished()"></div>
<div ng-repeat="thing in things" ng-init="$last && finished()">

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


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to jquery

How to make a variable accessible outside a function? Jquery assiging class to th in a table Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Getting all files in directory with ajax Bootstrap 4 multiselect dropdown Cross-Origin Read Blocking (CORB) bootstrap 4 file input doesn't show the file name Jquery AJAX: No 'Access-Control-Allow-Origin' header is present on the requested resource how to remove json object key and value.?

Examples related to angularjs

AngularJs directive not updating another directive's scope ERROR in Cannot find module 'node-sass' CORS: credentials mode is 'include' CORS error :Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response WebSocket connection failed: Error during WebSocket handshake: Unexpected response code: 400 Print Html template in Angular 2 (ng-print in Angular 2) $http.get(...).success is not a function Angular 1.6.0: "Possibly unhandled rejection" error Find object by its property in array of objects with AngularJS way Error: Cannot invoke an expression whose type lacks a call signature

Examples related to angularjs-ng-repeat

Angular ng-repeat add bootstrap row every 3 or 4 cols ng-if, not equal to? Understanding the ngRepeat 'track by' expression Calculating sum of repeated elements in AngularJS ng-repeat Angular.js ng-repeat filter by property having one of multiple values (OR of values) Using ng-if as a switch inside ng-repeat? In Angular, how to pass JSON object/array into directive? how to split the ng-repeat data with three columns using bootstrap Preserve line breaks in angularjs ng-repeat: access key and value for each object in array of objects

Examples related to document-ready

ng-repeat finish event How can I run a directive after the dom has finished rendering? $(document).ready shorthand