Edit to add a clear question: I have a flat array of some length and I want to put it into a tr/td type view? This might also be in a bootstrap grid or something like that. Essentially I want to display a flat array in a series of length-n chunks.
There are a number of variations of this question on SO but I haven't really seen a good explanation of either: how to make this work or why it can't. So I've made an extremely simple example that demonstrates the problem. It will render, but if you check the logs you'll see errors (which are too large to link to).
index.html:
<!DOCTYPE html>
<html lang="en-US" ng-app="rowApp">
<head><title>Angular chunks</title>
<script src=".4.9/angular.min.js" integrity="sha384-c4XWi4+MS7dBmCkPfB02+p/ExOF/ZBOfD2S4KR6mkmpBOg7IM6SUpA1KYZaVr7qE" crossorigin="anonymous"></script>
<script src="app.js"></script>
</head>
<body>
<table ng-controller="rowController" border="1" style="width:30%">
<tr ng-repeat="people_chunk in people | chunk:4">
<td ng-repeat="person in people_chunk">{{person.name}}</td>
</td>
</table>
</body>
</html>
app.js:
var rowApp = angular.module('rowApp', ['filters']);
angular.module('filters', []).
filter('chunk', function () {
return function (items, chunk_size) {
var chunks = [];
if (angular.isArray(items)) {
if (isNaN(chunk_size))
chunk_size = 4;
for (var i = 0; i < items.length; i += chunk_size) {
chunks.push(items.slice(i, i + chunk_size));
}
} else {
console.log("items is not an array: " + angular.toJson(items));
}
return chunks;
};
});
rowApp.controller('rowController',
function ($scope, $http) {
$http.get("/people.json")
.then(function(response) { $scope.people = response.data; });
});
people.json:
[{"name": "a"}, {"name": "b"}, {"name": "c"}, {"name": "d"},
{"name": "1"}, {"name": "2"}, {"name": "3"}, {"name": "4"}]
You can then serve all this with python -m SimpleHTTPServer 9000
and then go to http://localhost:9000/
Edit to add a clear question: I have a flat array of some length and I want to put it into a tr/td type view? This might also be in a bootstrap grid or something like that. Essentially I want to display a flat array in a series of length-n chunks.
There are a number of variations of this question on SO but I haven't really seen a good explanation of either: how to make this work or why it can't. So I've made an extremely simple example that demonstrates the problem. It will render, but if you check the logs you'll see errors (which are too large to link to).
index.html:
<!DOCTYPE html>
<html lang="en-US" ng-app="rowApp">
<head><title>Angular chunks</title>
<script src="https://ajax.googleapis./ajax/libs/angularjs/1.4.9/angular.min.js" integrity="sha384-c4XWi4+MS7dBmCkPfB02+p/ExOF/ZBOfD2S4KR6mkmpBOg7IM6SUpA1KYZaVr7qE" crossorigin="anonymous"></script>
<script src="app.js"></script>
</head>
<body>
<table ng-controller="rowController" border="1" style="width:30%">
<tr ng-repeat="people_chunk in people | chunk:4">
<td ng-repeat="person in people_chunk">{{person.name}}</td>
</td>
</table>
</body>
</html>
app.js:
var rowApp = angular.module('rowApp', ['filters']);
angular.module('filters', []).
filter('chunk', function () {
return function (items, chunk_size) {
var chunks = [];
if (angular.isArray(items)) {
if (isNaN(chunk_size))
chunk_size = 4;
for (var i = 0; i < items.length; i += chunk_size) {
chunks.push(items.slice(i, i + chunk_size));
}
} else {
console.log("items is not an array: " + angular.toJson(items));
}
return chunks;
};
});
rowApp.controller('rowController',
function ($scope, $http) {
$http.get("/people.json")
.then(function(response) { $scope.people = response.data; });
});
people.json:
[{"name": "a"}, {"name": "b"}, {"name": "c"}, {"name": "d"},
{"name": "1"}, {"name": "2"}, {"name": "3"}, {"name": "4"}]
You can then serve all this with python -m SimpleHTTPServer 9000
and then go to http://localhost:9000/
- Do you want to split an array in 4, and ng-repeat over them? – RaidenF Commented Feb 7, 2016 at 16:19
- Really not clear what your objective is. – charlietfl Commented Feb 7, 2016 at 16:34
- The error messages that you're getting, are probably because of an infinite loop. You can see if you use breakpoints, that your filter is executed till angular kills it. – RaidenF Commented Feb 7, 2016 at 16:35
- @K.Gkinis OK, that seems possible. But I'm not clear why. (I put an explicit questionat the start) – Kevin Lyda Commented Feb 7, 2016 at 18:41
- I think this is presentation specific logic, should be acplished using CSS – Tamas Hegedus Commented Feb 7, 2016 at 18:43
3 Answers
Reset to default 3You can avoid an infinite digest loop by simply memoizing your chunk function. This solves the issue of ng-repeat never finding the proper references and always thinking you are returning new items causing the infinite $digest.
angular.module('filters', []).
filter('chunk', function () {
function cacheIt(func) {
var cache = {};
return function(arg, chunk_size) {
// if the function has been called with the argument
// short circuit and use cached value, otherwise call the
// cached function with the argument and save it to the cache as well then return
return cache[arg] ? cache[arg] : cache[arg] = func(arg,chunk_size);
};
}
// unchanged from your example apart from we are no longer directly returning this
function chunk(items, chunk_size) {
var chunks = [];
if (angular.isArray(items)) {
if (isNaN(chunk_size))
chunk_size = 4;
for (var i = 0; i < items.length; i += chunk_size) {
chunks.push(items.slice(i, i + chunk_size));
}
} else {
console.log("items is not an array: " + angular.toJson(items));
}
return chunks;
}
// now we return the cached or memoized version of our chunk function
// if you want to use lodash this is really easy since there is already a chunk and memoize function all above code would be removed
// this return would simply be: return _.memoize(_.chunk);
return cacheIt(chunk);
});
As the filter is returning new array instances as slices, angular's ngRepeat
will detect them changed (as ngRepeat
uses $watchCollection
internally), and will cause and infinite digest loop. There is even an issue about that, but it is abandoned since 2013: https://github./angular/angular.js/issues/2033
This issue still persists if you change the snippet to contain a non-constant expression, like [[x]]
:
<div ng-repeat="a in [[x]]">
<div ng-repeat="b in a">
</div>
</div>
I am afraid you will have to move the chunking logic into the controller, or to use css to form a 4-width table.
maybe this could work to someone, you never know
the following code will assign an extra value (store_chunk) to an array of stores. so I can use with ng-repeat to show 3 different columns in my HTML
var x = 0;
var y = 1;
var how_many_chunks = 3;
var limit = $scope.main.stores.length / how_many_chunks ;
angular.forEach($scope.main.stores, function(e, key) {
if (x <= limit) {
$scope.main.stores[key].store_chunk = y;
}
else{
y += 1;
limit = y * limit;
$scope.main.stores[key].store_chunk = y;
}
x += 1;
});
here the HTML
<div class="row">
<div class="col-xs-12 col-sm-3 col-md-3 col-lg-3">
<ul class="main_report">
<li ng-repeat="store in main.stores | filter:{ store_chunk: 3 }">{{store.store_name}}</li>
</ul>
</div>
<div class="col-xs-12 col-sm-3 col-md-3 col-lg-3">
<ul class="main_report">
<li ng-repeat="store in main.stores | filter:{ store_chunk: 3 }">{{store.store_name}}</li>
</ul>
</div>
<div class="col-xs-12 col-sm-3 col-md-3 col-lg-3">
<ul class="main_report">
<li ng-repeat="store in main.stores | filter:{ store_chunk: 3 }">{{store.store_name}}</li>
</ul>
</div>
</div>
this works like a charm! and don't vote down just because you didn't like it!.. thumbs up instead!