最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - AngularJS: Can I use a filter to chunk an array in ng-repeat? - Stack Overflow

programmeradmin3浏览0评论

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/

Share Improve this question edited Feb 7, 2016 at 18:40 Kevin Lyda asked Feb 7, 2016 at 16:03 Kevin LydaKevin Lyda 9999 silver badges14 bronze badges 5
  • 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
Add a ment  | 

3 Answers 3

Reset to default 3

You 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!

发布评论

评论列表(0)

  1. 暂无评论