how to split the ng-repeat data with three columns using bootstrap

JavascriptAngularjsAngularjs Ng-Repeat

Javascript Problem Overview


I am using ng-repeat with my code I have 'n' number of text box based on ng-repeat. I want to align the textbox with three columns.

this is my code

<div class="control-group" ng-repeat="oneExt in configAddr.ext">
    {{$index+1}}. 
    <input type="text" name="macAdr{{$index+1}}" 
           id="macAddress" ng-model="oneExt.newValue" value=""/>
</div>

Javascript Solutions


Solution 1 - Javascript

The most reliable and technically correct approach is to transform the data in the controller. Here's a simple chunk function and usage.

function chunk(arr, size) {
  var newArr = [];
  for (var i=0; i<arr.length; i+=size) {
    newArr.push(arr.slice(i, i+size));
  }
  return newArr;
}

$scope.chunkedData = chunk(myData, 3);

Then your view would look like this:

<div class="row" ng-repeat="rows in chunkedData">
  <div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>

If you have any inputs within the ng-repeat, you will probably want to unchunk/rejoin the arrays as the data is modified or on submission. Here's how this would look in a $watch, so that the data is always available in the original, merged format:

$scope.$watch('chunkedData', function(val) {
  $scope.data = [].concat.apply([], val);
}, true); // deep watch

Many people prefer to accomplish this in the view with a filter. This is possible, but should only be used for display purposes! If you add inputs within this filtered view, it will cause problems that can be solved, but are not pretty or reliable.

The problem with this filter is that it returns new nested arrays each time. Angular is watching the return value from the filter. The first time the filter runs, Angular knows the value, then runs it again to ensure it is done changing. If both values are the same, the cycle is ended. If not, the filter will fire again and again until they are the same, or Angular realizes an infinite digest loop is occurring and shuts down. Because new nested arrays/objects were not previously tracked by Angular, it always sees the return value as different from the previous. To fix these "unstable" filters, you must wrap the filter in a memoize function. lodash has a memoize function and the latest version of lodash also includes a chunk function, so we can create this filter very simply using npm modules and compiling the script with browserify or webpack.

Remember: display only! Filter in the controller if you're using inputs!

Install lodash:

npm install lodash-node

Create the filter:

var chunk = require('lodash-node/modern/array/chunk');
var memoize = require('lodash-node/modern/function/memoize');

angular.module('myModule', [])
.filter('chunk', function() {
  return memoize(chunk);
});

And here's a sample with this filter:

<div ng-repeat="row in ['a','b','c','d','e','f'] | chunk:3">
  <div class="column" ng-repeat="item in row">
    {{($parent.$index*row.length)+$index+1}}. {{item}}
  </div>
</div>

Order items vertically

1  4
2  5
3  6

Regarding vertical columns (list top to bottom) rather than horizontal (left to right), the exact implementation depends on the desired semantics. Lists that divide up unevenly can be distributed different ways. Here's one way:

<div ng-repeat="row in columns">
  <div class="column" ng-repeat="item in row">
    {{item}}
  </div>
</div>

var data = ['a','b','c','d','e','f','g'];
$scope.columns = columnize(data, 3);
function columnize(input, cols) {
  var arr = [];
  for(i = 0; i < input.length; i++) {
    var colIdx = i % cols;
    arr[colIdx] = arr[colIdx] || [];
    arr[colIdx].push(input[i]);
  }
  return arr;
}

However, the most direct and just plainly simple way to get columns is to use CSS columns:

.columns {
  columns: 3;
}

<div class="columns">
  <div ng-repeat="item in ['a','b','c','d','e','f','g']">
    {{item}}
  </div>
</div>

Solution 2 - Javascript

This solution is very simple:

JSON:

[{id:"1",name:"testA"},{id:"2",name:"test B"},{id:"3",name:"test C"},{id:"4",name:"test D"},{id:"5",name:"test E"}]

HTML:

<div ng-controller="MyCtrl">
  <table>
	<tr ng-repeat="item in items" ng-switch on="$index % 3">
	  <td ng-switch-when="0">
		{{items[$index].id}} {{items[$index].name}}
	  </td>
	  <td ng-switch-when="0">
		<span ng-show="items[$index+1]">
		  {{items[$index+1].id}} {{items[$index+1].name}}
		</span>
	  </td>
	  <td ng-switch-when="0">
		<span ng-show="items[$index+2]">    
		  {{items[$index+2].id}} {{items[$index+2].name}}
		</span>
	  </td>
	</tr>
  </table>
</div>

DEMO in FIDDLE

enter image description here

Solution 3 - Javascript

A clean, adaptable solution that does not require data manipulation:

The HTML:

<div class="control-group" class="label"
    ng-repeat="oneExt in configAddr.ext"
    ng-class="{'new-row': startNewRow($index, columnBreak) }">
    {{$index+1}}. 
    <input type="text" name="macAdr{{$index+1}}" 
       id="macAddress{{$index}}" ng-model="oneExt.newValue" />
</div>

The CSS:

.label {
    float: left;
    text-align: left;
}
.new-row {
    clear: left;
}

The JavaScript:

$scope.columnBreak = 3; //Max number of colunms
$scope.startNewRow = function (index, count) {
    return ((index) % count) === 0;
};

This is a simple solution for rendering data into rows and columns dynamically with no need to manipulate the array of data you are trying to display. Plus if you try resizing the browser window you can see the grid dynamically adapts to the size of the screen/div.

(I also added an {{$index}} suffix to your id since ng-repeat will try to create multiple elements with the same id if you do not.)

A similar working example

Solution 4 - Javascript

m59's answer is pretty good. The one thing I don't like about it is that it uses divs for what could potentially be data for a table.

So in conjunction with m59's filter (answer somewhere above), here is how to display it in a table.

<table>
    <tr class="" ng-repeat="rows in foos | chunk:2">
        <td ng-repeat="item in rows">{{item}}</td>
    </tr>
</table>

Solution 5 - Javascript

Following is a more simple way:

<table>
    <tr ng-repeat="item in lists" ng-hide="$index%2!==0">
        <td>
            <label>{{ lists[$index].name}}</label>
        </td>
        <td ng-hide="!lists[$index+1]">
            <label>{{ lists[$index+1].name}}</label>
        </td>
    </tr>
</table>

Cumulo Nimbus's answer is useful for me but I want this grid wrapped by a div which can show the scrollbar when the list is too long.

To achieve this I added style="height:200px; overflow:auto" to a div around the table which causes it to show as a single column.

Now works for array length of one.

Solution 6 - Javascript

I have a function and stored it in a service so i can use it all over my app:

Service:

app.service('SplitArrayService', function () {
return {
    SplitArray: function (array, columns) {
        if (array.length <= columns) {
            return [array];
        };

        var rowsNum = Math.ceil(array.length / columns);

        var rowsArray = new Array(rowsNum);

        for (var i = 0; i < rowsNum; i++) {
            var columnsArray = new Array(columns);
            for (j = 0; j < columns; j++) {
                var index = i * columns + j;

                if (index < array.length) {
                    columnsArray[j] = array[index];
                } else {
                    break;
                }
            }
            rowsArray[i] = columnsArray;
        }
        return rowsArray;
    }
}

});

Controller:

$scope.rows   = SplitArrayService.SplitArray($scope.images, 3); //im splitting an array of images into 3 columns

Markup:

 <div class="col-sm-12" ng-repeat="row in imageRows">
     <div class="col-sm-4" ng-repeat="image in row">
         <img class="img-responsive" ng-src="{{image.src}}">
     </div>
 </div>

Solution 7 - Javascript

My approach was a mixture of things.

My objective was to have a grid adapting to the screen size. I wanted 3 columns for lg, 2 columns for sm and md, and 1 column for xs.

First, I created the following scope function, using angular $window service:

$scope.findColNumberPerRow = function() {
    var nCols;
    var width = $window.innerWidth;

    if (width < 768) {
	    // xs
	    nCols = 1;
    }
    else if(width >= 768 && width < 1200) {
	     // sm and md
	     nCols = 2
    } else if (width >= 1200) {
	    // lg
	    nCols = 3;
    }
    return nCols;
};

Then, I used the class proposed by @Cumulo Nimbus:

.new-row {
    clear: left;
}

In the div containing the ng-repeat, I added the resizable directive, as explained in this page, so that every time window is resized, angular $window service is updated with the new values.

Ultimately, in the repeated div I have:

ng-repeat="user in users" ng-class="{'new-row': ($index % findColNumberPerRow() === 0) }"

Please, let me know any flaws in this approach.

Hope it can be helpful.

Solution 8 - Javascript

A simple trick with "clearfix" CSS recommended by Bootstrap:

{{value}}

Many advantages: Efficient, fast, using Boostrap recommendations, avoiding possible $digest issues, and not altering Angular model.

Solution 9 - Javascript

This example produces a nested repeater where the outer data includes an inner array which I wanted to list in two column. The concept would hold true for three or more columns.

In the inner column I repeat the "row" until the limit is reached. The limit is determined by dividing the length of the array of items by the number of columns desired, in this case by two. The division method sits on the controller and is passed the current array length as a parameter. The JavaScript slice(0, array.length / columnCount) function then applied the limit to the repeater.

The second column repeater is then invoked and repeats slice( array.length / columnCount, array.length) which produces the second half of the array in column two.

<div class="row" ng-repeat="GroupAccess in UsersController.SelectedUser.Access" ng-class="{even: $even, odd: $odd}">
    <div class="col-md-12 col-xs-12" style=" padding-left:15px;">
        <label style="margin-bottom:2px;"><input type="checkbox" ng-model="GroupAccess.isset" />{{GroupAccess.groupname}}</label>
    </div>
    <div class="row" style=" padding-left:15px;">
        <div class="col-md-1 col-xs-0"></div>
        <div class="col-md-3 col-xs-2">
            <div style="line-height:11px; margin-left:2px; margin-bottom:2px;" ng-repeat="Feature in GroupAccess.features.slice(0, state.DivideBy2(GroupAccess.features.length))">
                <span class="GrpFeature">{{Feature.featurename}}</span>
            </div>
        </div>
        <div class="col-md-3 col-xs-2">
            <div style="line-height:11px; margin-left:2px; margin-bottom:2px;" ng-repeat="Feature in GroupAccess.features.slice( state.DivideBy2(GroupAccess.features.length), GroupAccess.features.length )">
                <span class="GrpFeature">{{Feature.featurename}}</span>
            </div>
        </div>
        <div class="col-md-5 col-xs-8">
        </div>
    </div>
</div>


// called to format two columns
state.DivideBy2 = function(nValue) {
    return Math.ceil(nValue /2);
};

Hope this helps to look at the solution in yet another way. (PS this is my first post here! :-))

Solution 10 - Javascript

I fix without .row

<div class="col col-33 left" ng-repeat="photo in photos">
   Content here...
</div>

and css

.left {
  float: left;
}

Solution 11 - Javascript

<div class="row">
  <div class="col-md-4" ng-repeat="remainder in [0,1,2]">
    <ul>
      <li ng-repeat="item in items" ng-if="$index % 3 == remainder">{{item}}</li>
    </ul>
  </div>
</div>

Solution 12 - Javascript

Here's an easy-hacky way of doing it. It's more manual and ends up with messy markup. I do not recommend this, but there are situations where this might be useful.

Here's a fiddle link http://jsfiddle.net/m0nk3y/9wcbpydq/

HTML:

<div ng-controller="myController">
    <div class="row">
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3)">
                {{ person.name }}
                </div>
            </div>
        </div>
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3):Math.ceil(data.length/3)">
                {{ person.name }}
                </div>
            </div>
        </div>
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3):Math.ceil(data.length/3)*2">
                {{ person.name }}
                </div>
            </div>
        </div>
    </div>
</div>

JS:

var app = angular.module('myApp', []);

app.controller('myController', function($scope) {
    
	$scope.Math = Math;
	$scope.data = [
        {"name":"Person A"},
        ...
    ];
});

This setup requires us to use some Math on the markup :/, so you'll need to inject Math by adding this line: $scope.Math = Math;

Solution 13 - Javascript

All of these answers seem massively over engineered.

By far the simplest method would be to set up the input divs in a col-md-4 column bootstrap, then bootstrap will automatically format it into 3 columns due to the 12 column nature of bootstrap:

<div class="col-md-12">
    <div class="control-group" ng-repeat="oneExt in configAddr.ext">
        <div class="col-md-4">
            <input type="text" name="macAdr{{$index}}"
                   id="macAddress" ng-model="oneExt.newValue" value="" />
        </div>
    </div>
</div>


Solution 14 - Javascript

Basing off of m59's very good answer. I found that model inputs would be blurred if they changed, so you could only change one character at a time. This is a new one for lists of objects for anyone that needs it:

> EDIT Updated to handle multiple filters on one page

app.filter('partition', function() {
  var cache = {}; // holds old arrays for difference repeat scopes
  var filter = function(newArr, size, scope) {
    var i,
      oldLength = 0,
      newLength = 0,
      arr = [],
      id = scope.$id,
      currentArr = cache[id];
    if (!newArr) return;
  
    if (currentArr) {
      for (i = 0; i < currentArr.length; i++) {
        oldLength += currentArr[i].length;
      }
    }
    if (newArr.length == oldLength) {
      return currentArr; // so we keep the old object and prevent rebuild (it blurs inputs)
    } else {
      for (i = 0; i < newArr.length; i += size) {
        arr.push(newArr.slice(i, i + size));
      }
      cache[id] = arr;
      return arr;
    }
  };
  return filter;
}); 

And this would be the usage:

<div ng-repeat="row in items | partition:3:this">
  <span class="span4">
    {{ $index }}
  </span>
</div>

Solution 15 - Javascript

I'm new in bootstrap and angularjs, but this could also make Array per 4 items as one group, the result will almost like 3 columns. This trick use bootstrap break line principle.

<div class="row">
 <div class="col-sm-4" data-ng-repeat="item in items">
  <div class="some-special-class">
   {{item.XX}}
  </div>
 </div>
</div>

Solution 16 - Javascript

Another way is set width:33.33%; float:left to a wrapper div like this:

<div ng-repeat="right in rights" style="width: 33.33%;float: left;">
    <span style="width:60px;display:inline-block;text-align:right">{{$index}}</span>
    <input type="number" style="width:50px;display:inline-block" ">
</div>

Solution 17 - Javascript

Lodash has a chunk method built-in now which I personally use: https://lodash.com/docs#chunk

Based on this, controller code might look like the following:

$scope.groupedUsers = _.chunk( $scope.users, 3 )

View code:

<div class="row" ng-repeat="rows in groupedUsers">
  <div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>

Solution 18 - Javascript

I found myself in a similar case, wanting to generate display groups of 3 columns each. However, although I was using bootstrap, I was trying to separate these groups into different parent divs. I also wanted to make something generically useful.

I approached it with 2 ng-repeat as below:

<div ng-repeat="items in quotes" ng-if="!($index % 3)">
  <div ng-repeat="quote in quotes" ng-if="$index <= $parent.$index + 2 && $index >= $parent.$index">
                ... some content ...
  </div>
</div>

This makes it very easy to change to a different number of columns, and separated out into several parent divs.

Solution 19 - Javascript

Just in case someone wants an Angular 7 (and higher version) here is an example I used in one of my applications:

HTML:

                   <div class="container-fluid">
                    <div class="row" *ngFor="let reports of chunkData; index as i">
                        <div *ngFor="let report of reports" class="col-4 col-sm-4"
                            style="border-style:solid;background-color: antiquewhite">{{report}}</div>
                    </div>
                </div>

.TS FILE

export class ConfirmationPageComponent implements OnInit {
  chunkData = [];
  reportsArray = ["item 1", "item 2", "item 3", "item 4", "item 5", "item 6"];
  
  constructor() {}

  ngOnInit() {
    this.chunkData = this.chunk(this.reportsArray, 3);
  }

  chunk(arr, size) {
    var newArr = [];
    for (var i = 0; i < arr.length; i += size) {
      newArr.push(arr.slice(i, i + size));
    }
    return newArr;
  }
}

This is an awesome solution for dynamically creating new columns/rows depending on how much items your iterating from the database. THanks!

Solution 20 - Javascript

this answers the original question which is how to get 1,2,3 in a column. – asked by kuppu Feb 8 '14 at 13:47

angularjs code:

function GetStaffForFloor(floor) {
    var promiseGet = Directory_Service.getAllStaff(floor);
    promiseGet.then(function (pl) {
        $scope.staffList = chunk(pl.data, 3); //pl.data; //
    },
    function (errorOD) {
        $log.error('Errored while getting staff list.', errorOD);
    });
}
function chunk(array, columns) {
    var numberOfRows = Math.ceil(array.length / columns);

    //puts 1, 2, 3 into column
    var newRow = []; //array is row-based.
    for (var i = 0; i < array.length; i++) {
        var columnData = new Array(columns);
        if (i == numberOfRows) break;
        for (j = 0; j < columns; j++)
        {
            columnData[j] = array[i + numberOfRows * j];
        }
        newRow.push(columnData); 
    }
    return newRow;

    ////this works but 1, 2, 3 is in row
    //var newRow = [];
    //for (var i = 0; i < array.length; i += columns) {
    //    newRow.push(array.slice(i, i + columns)); //push effectively does the pivot. array is row-based.
    //}
    //return newRow;
};

View Code (note: using bootstrap 3):

<div class="staffContainer">
    <div class="row" ng-repeat="staff in staffList">
        <div class="col-md-4" ng-repeat="item in staff">{{item.FullName.length > 0 ? item.FullName + ": Rm " + item.RoomNumber : ""}}</div>
    </div>
</div>

Solution 21 - Javascript

This code will help to align the elements with three columns in lg, and md mode, two column in sm mode, and single column is xs mode

<div class="row">
<div ng-repeat="oneExt in configAddr.ext">
	<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
		{$index+1}}. 
		<input type="text" name="macAdr{{$index+1}}" 
       id="macAddress" ng-model="oneExt.newValue" value=""/>
	</div>
</div>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkuppuView Question on Stackoverflow
Solution 1 - Javascriptm59View Answer on Stackoverflow
Solution 2 - JavascriptStéphane GRILLONView Answer on Stackoverflow
Solution 3 - JavascriptCumulo NimbusView Answer on Stackoverflow
Solution 4 - JavascriptJarrodView Answer on Stackoverflow
Solution 5 - JavascriptgetingView Answer on Stackoverflow
Solution 6 - JavascriptChanchoView Answer on Stackoverflow
Solution 7 - JavascriptigorauadView Answer on Stackoverflow
Solution 8 - JavascriptLastnicoView Answer on Stackoverflow
Solution 9 - JavascriptHarvey MushmanView Answer on Stackoverflow
Solution 10 - JavascriptselahattinunluView Answer on Stackoverflow
Solution 11 - JavascriptmrdedView Answer on Stackoverflow
Solution 12 - JavascriptGene ParcellanoView Answer on Stackoverflow
Solution 13 - JavascriptcullimorerView Answer on Stackoverflow
Solution 14 - JavascriptJSousView Answer on Stackoverflow
Solution 15 - JavascriptNathanView Answer on Stackoverflow
Solution 16 - JavascriptgetingView Answer on Stackoverflow
Solution 17 - Javascriptsean2078View Answer on Stackoverflow
Solution 18 - JavascriptMatt KindyView Answer on Stackoverflow
Solution 19 - JavascriptGelView Answer on Stackoverflow
Solution 20 - Javascriptuser2378769View Answer on Stackoverflow
Solution 21 - JavascriptHariView Answer on Stackoverflow