ng-repeat finish event

208,910

Solution 1

Indeed, you should use directives, and there is 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!

Solution 2

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.

Solution 3

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()">

Solution 4

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

    })

Solution 5

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.

Share:
208,910

Related videos on Youtube

ChruS
Author by

ChruS

Updated on April 04, 2020

Comments

  • ChruS
    ChruS about 4 years

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

  • ChruS
    ChruS over 11 years
    Can I make myMainDirective's code execute only after end of loop? I need to update scrollbar of parent div.
  • Tiago Roldão
    Tiago Roldão over 11 years
    Of course! Just change the "window.alert" to an event firing function, and catch it with the main directive. I updated de Plunker to do this, as an example.
  • Mark Rajcok
    Mark Rajcok over 11 years
    It is recommended to include the jQuery library first (before Angular). This will cause all Angular elements to be wrapped with jQuery (instead of jqLite). Then instead of angular.element(element).css() and $(element).children().css() you can simply write element.css() and element.children().css(). See also groups.google.com/d/msg/angular/6A3Skwm59Z4/oJ0WhKGAFK0J
  • Neil
    Neil over 10 years
    Can you tell me if there is a similar method for ng-options? I need to know when angular has built my select box but there is no ng-repeat
  • Tiago Roldão
    Tiago Roldão over 10 years
    ng-options is a optional helper attibute, and too simple to have this functionality. You can use ng-repeat to create your options (if you are happy with only text values for your options), and trigger your event the same way as described here. It would halp to know what you need to do with the select element.
  • Tiago Roldão
    Tiago Roldão over 10 years
    Also, checkout the select2 directive, from angular-ui: github.com/angular-ui/ui-select2 - its code may give you some ideas.
  • RavenHursT
    RavenHursT over 10 years
    Any1 have any idea how to get this to work for subsequent renders on the ngRepeat directive? i.e: link
  • Jørgen Skår Fischer
    Jørgen Skår Fischer over 9 years
    Should note that you need the lodash.js for this to work :) Also: the ",20" part of the $scope.refresh = , is that number of millis before this method is called after the last itteration?
  • Pavel Horal
    Pavel Horal over 9 years
    Added Lodash in front of the debounce link. Yes, 20 is delay before the method is executed - changed to 0 as it does not matter as long as the call is async.
  • Pavel Horal
    Pavel Horal over 9 years
    Also thinking about this, as ternary operator is allowed in expressions simple ng-init="$last ? refresh() : false" would work as well. And that does not even require Lodash.
  • xzegga
    xzegga over 9 years
    This work fine if you need fire function after last ng-repeat item is loaded, but last is true before data is rendered in html, if you put a breakpoint in your window.alert line notese that data has not yet been propertly rendered, is there an implementation to fire a function when data was rendered in browser browser?
  • Tiago Roldão
    Tiago Roldão over 9 years
    There aren't any events for when the element is rendered, as this framework assumes a continuous updating system. If you move the $emit function inside the $watch for 'thing', however, it will get called whenever that changes. If you only need to call this once, you may try using element.ready(). It really depends on what you need, specifically. See this updated plunker (and try uncommenting the code in $watch): plnkr.co/edit/LzFP1Maee0gWuEOxFahl?p=preview
  • chovy
    chovy over 9 years
    i found same issue. both in backbone + angular, if you need to do something like read css property values applied to a DOM element, I could not figure out a way w/o the timeout hack.
  • Timo Ernst
    Timo Ernst about 9 years
    Why is the directive name in html written using dashed (-) but in JavaScript you used uppercase letters? my-repeat-directive vs. myRepeatDirective
  • Tiago Roldão
    Tiago Roldão about 9 years
    This is a naming convention for all angular directives. See docs.angularjs.org/guide/directive#normalization
  • JoshGough
    JoshGough almost 9 years
    Beautiful, this did the job. I wanted to auto-select text in a textbox, and the timeout did the trick. Otherwise, the {{model.value}} text got selected and then deselected when the data-bound model.value was injected.
  • Tejasvi Hegde
    Tejasvi Hegde almost 9 years
    Wouldn't it be better idea to use directives as attribs than elements ? i.e. restrict: 'A' ?
  • JoshuaDavid
    JoshuaDavid almost 9 years
    The point was to show declarative syntax + isolate scope... yes, you are welcome to use an attribute directive here if you desire. There is a time and place for either depending on your purpose.
  • Nate Whittaker
    Nate Whittaker over 8 years
    Why use a ternary when you can use boolean logic: ng-init="$last && doSomething( )"
  • Jameela Huq
    Jameela Huq almost 8 years
    I didn't want to use a $timeout, but when you said it could be set to 0, I decided to give it a try and it worked well. I wonder if this is a matter of digestion, and if there is a way to do what the $timeout is doing without using $timeout.
  • Frane Poljak
    Frane Poljak over 7 years
    What if "things" turn out to be an empty array?
  • Vikas Bansal
    Vikas Bansal over 7 years
    @FranePoljak I have add the solution in the answer.
  • JSAddict
    JSAddict over 7 years
    <div ng-repeat="i in [1,2,4]" ng-init="doSomething($last)"></div> $scope.doSomething = function(lastElement) { if(lastElem) blah blah blah }
  • JSAddict
    JSAddict over 7 years
    In case of empty array, handle it in controller
  • Alex Teslenko
    Alex Teslenko about 7 years
    Fix Vikas Bansal answer: //use first div if you want check if the array is not having any value and call the function accordingly. <div ng-if="!things.length" ng-hide="true" ng-init="finished()"></div> <div ng-repeat="thing in things" ng-init="$last && finished()">
  • Dan Nguyen
    Dan Nguyen about 7 years
    Does this work if i utilize controller as syntax? Please? In not, are there any other way that works with controller as?
  • Dan Nguyen
    Dan Nguyen about 7 years
    Does this work if i utilize controller as syntax? Please? In not, are there any other way that works with controller as?
  • Jin
    Jin almost 6 years
    Can you please explain why does this work? I'm trying to access dynamic ID's and it only works after having a timeout with 0 delay. I saw this "hacky" solution in several posts but no one explains why it works.
  • Justin
    Justin about 4 years
    Its' easier to read @NateWhittaker (and being harder to read than a ternary operator is impressive). It's good to have clean, and simple to understand code