9

I am trying to figure out how to dynamically create bootstrap row divs with a class of row-fluid with angular.js using the ng-repeat directive.

Here is the angular:

 <div ng-repeat="task in tasks" class="row-fluid">
     <div class="span6 well">{{task.name}}</div>
 </div>

This does not work though. The bootstrap html I wish to generate is:

http://jsfiddle.net/YKkXA/2/

Basically I need to do mod 2 of the index inside of the ng-repeat, and if its 0, close out the </div> and create a new <div class="row-fluid">. How is this possible?

Justin
  • 42,716
  • 77
  • 201
  • 296
  • You can use the predefined variable _$index_ inside the _ng-repeat_ loop – remigio Feb 07 '13 at 10:47
  • Can you give an example, how exactly would `$index` help us? We need basically an `if` statement inside of the `ng-repeat`. – Justin Feb 07 '13 at 10:49
  • I watched the example, there is no way to do it using _ngRepeat_. You should write a custom directive, creating the html dinamically – remigio Feb 07 '13 at 11:33
  • You should accept Anthony's answer to this - it solves the problem, and as this is the first result when searching for this in Google it should be pointed out that it's the right way to do it. – JVG Jul 10 '13 at 13:05
  • See my answer here for a filter to handle this that does NOT cause an infinite digest: http://stackoverflow.com/a/21653981/1435655 – m59 Feb 09 '14 at 01:52

3 Answers3

14

The idea is to filter your items in order to group them, and make a second ngRepeat to iterate on sub-items.

First, add that filter to your module :

module.filter('groupBy', function() {
    return function(items, groupedBy) {
        if (items) {
            var finalItems = [],
                thisGroup;
            for (var i = 0; i < items.length; i++) {
                if (!thisGroup) {
                    thisGroup = [];
                }
                thisGroup.push(items[i]);
                if (((i+1) % groupedBy) === 0) {
                    finalItems.push(thisGroup);
                    thisGroup = null;
                }
            }
            if (thisGroup) {
                finalItems.push(thisGroup);
            }
            return finalItems;
        }
    };
});

In your controler (because if you filter directly in your template, then you will have a $digest loop):

function TaskCtrl() {
    $scope.tasksGroupBy2 = $filter('groupBy')(taskGroup, 2);
}

And in your .html :

<div ng-repeat="taskGroup in tasksGroupBy2" class="row-fluid">
    <div ng-repeat="task in taskGroup" class="span6 well">{{task.name}}</div>
</div>
Community
  • 1
  • 1
Anthony O.
  • 22,041
  • 18
  • 107
  • 163
  • To anyone stumbling across this, this should be the accepted answer - worked just fine for me. – JVG Jul 10 '13 at 13:03
  • On the one hand, this avoids using complicated directives (which will ng-repeat on all the items and create the rows and columns dynamically). On the other - the items have now been split up, making traversing them and other 'all items' functions with 2-way binding less... nice :-P – Tom Teman Aug 05 '13 at 21:03
  • 1
    Here's how to do this in the view without the infinite digest: http://stackoverflow.com/a/21653981/1435655 – m59 Feb 09 '14 at 01:53
  • Here is a gist including the test for it: https://gist.github.com/mrzmyr/10972141 – e382df99a7950919789725ceeec126 Apr 17 '14 at 10:31
4

As an improvement to the answer Anthony gave, I would say that you could save yourself a lot of trouble using the slice method instead of going through all those conditions.

Try defining your filter as it follows:

module.filter('group', function() {
    return function(items, groupItems) {
        if (items) {
            var newArray = [];

            for (var i = 0; i < items.length; i+=groupItems) {
                if (i + groupItems > items.length) {
                    newArray.push(items.slice(i));
                } else {
                    newArray.push(items.slice(i, i + groupItems));
                }
            }

            return newArray;
        }
    };
});

After that you can call the filter on your controller as Anthony pointed out in his response:

function Controller ($scope) {
    $scope.itemsGrouped = $filter('group')(itemsArray, 5);
}
alex952
  • 41
  • 1
0

Off Topic: using bootstrap you can just place divs of class="span6 well" into one bigass row as they will stack nicely. (You will get an responsive layout too). Sorry if it was just an example of the behaviour that bootstrap can't handle. Anthony and Remigio are right; you have to create an extra module vehicle that will generate divs immersed into your ng-repeated tags.

  • This is not entirely correct. Sometimes it works, othertimes it won't, depending on the height of the divs. – arg20 Oct 15 '13 at 15:09