【问题标题】:How can I group data with an Angular filter?如何使用 Angular 过滤器对数据进行分组?
【发布时间】:2013-01-25 21:20:04
【问题描述】:

我有一个玩家列表,每个玩家都属于一个组。如何使用过滤器列出每个组的用户?

[{name: 'Gene', team: 'team alpha'},
 {name: 'George', team: 'team beta'},
 {name: 'Steve', team: 'team gamma'},
 {name: 'Paula', team: 'team beta'},
 {name: 'Scruath of the 5th sector', team: 'team gamma'}];

我正在寻找这个结果:

  • 团队阿尔法
    • 基因
  • 团队测试版
    • 乔治
    • 宝拉
  • 团队伽玛
    • 史蒂夫
    • 第五区的斯克鲁斯

【问题讨论】:

    标签: javascript filter angularjs grouping


    【解决方案1】:

    首先使用一个只返回唯一球队的过滤器执行一个循环,然后是一个返回当前球队所有球员的嵌套循环:

    http://jsfiddle.net/plantface/L6cQN/

    html:

    <div ng-app ng-controller="Main">
        <div ng-repeat="playerPerTeam in playersToFilter() | filter:filterTeams">
            <b>{{playerPerTeam.team}}</b>
            <li ng-repeat="player in players | filter:{team: playerPerTeam.team}">{{player.name}}</li>        
        </div>
    </div>
    

    脚本:

    function Main($scope) {
        $scope.players = [{name: 'Gene', team: 'team alpha'},
                        {name: 'George', team: 'team beta'},
                        {name: 'Steve', team: 'team gamma'},
                        {name: 'Paula', team: 'team beta'},
                        {name: 'Scruath of the 5th sector', team: 'team gamma'}];
    
        var indexedTeams = [];
    
        // this will reset the list of indexed teams each time the list is rendered again
        $scope.playersToFilter = function() {
            indexedTeams = [];
            return $scope.players;
        }
    
        $scope.filterTeams = function(player) {
            var teamIsNew = indexedTeams.indexOf(player.team) == -1;
            if (teamIsNew) {
                indexedTeams.push(player.team);
            }
            return teamIsNew;
        }
    }
    

    【讨论】:

    • 太棒了。但是如果我想在点击时将新对象推送到 $scope.players 怎么办?当你循环一个函数时,它会被添加吗?
    【解决方案2】:

    我最初使用 Plantface 的答案,但我不喜欢我认为的语法。

    我对其进行了重新设计,以使用 $q.defer 对数据进行后处理并返回独特团队的列表,然后将其用作过滤器。

    http://plnkr.co/edit/waWv1donzEMdsNMlMHBa?p=preview

    查看

    <ul>
      <li ng-repeat="team in teams">{{team}}
        <ul>
          <li ng-repeat="player in players | filter: {team: team}">{{player.name}}</li> 
        </ul>
      </li>
    </ul>
    

    控制器

    app.controller('MainCtrl', function($scope, $q) {
    
      $scope.players = []; // omitted from SO for brevity
    
      // create a deferred object to be resolved later
      var teamsDeferred = $q.defer();
    
      // return a promise. The promise says, "I promise that I'll give you your
      // data as soon as I have it (which is when I am resolved)".
      $scope.teams = teamsDeferred.promise;
    
      // create a list of unique teams. unique() definition omitted from SO for brevity
      var uniqueTeams = unique($scope.players, 'team');
    
      // resolve the deferred object with the unique teams
      // this will trigger an update on the view
      teamsDeferred.resolve(uniqueTeams);
    
    });
    

    【讨论】:

    • 此答案不适用于 AngularJS > 1.1,因为 Promised 不再针对数组展开。见immigration notes
    • 此解决方案中不需要 Promise,因为您没有异步执行任何操作。在这种情况下,您可以直接跳过该步骤 (jsFiddle)。
    【解决方案3】:

    两个答案都很好,所以我将它们移到了一个指令中,这样它就可以重复使用,并且不必定义第二个作用域变量。

    Here is the fiddle 如果你想看到它的实现

    下面是指令:

    var uniqueItems = function (data, key) {
        var result = [];
        for (var i = 0; i < data.length; i++) {
            var value = data[i][key];
            if (result.indexOf(value) == -1) {
                result.push(value);
            }
        }
        return result;
    };
    
    myApp.filter('groupBy',
                function () {
                    return function (collection, key) {
                        if (collection === null) return;
                        return uniqueItems(collection, key);
            };
        });
    

    那么可以这样使用:

    <div ng-repeat="team in players|groupBy:'team'">
        <b>{{team}}</b>
        <li ng-repeat="player in players | filter: {team: team}">{{player.name}}</li>        
    </div>
    

    【讨论】:

      【解决方案4】:

      除了上面接受的答案之外,我还使用 underscore.js 库创建了一个通用的“groupBy”过滤器。

      JSFiddle(更新): http://jsfiddle.net/TD7t3/

      过滤器

      app.filter('groupBy', function() {
          return _.memoize(function(items, field) {
                  return _.groupBy(items, field);
              }
          );
      });
      

      注意“memoize”调用。这种下划线方法会缓存函数的结果,并阻止 angular 每次评估过滤器表达式,从而防止 angular 达到摘要迭代限制。

      html

      <ul>
          <li ng-repeat="(team, players) in teamPlayers | groupBy:'team'">
              {{team}}
              <ul>
                  <li ng-repeat="player in players">
                      {{player.name}}
                  </li>
              </ul>
          </li>
      </ul>
      

      我们在 teamPlayers 范围变量的“team”属性上应用我们的“groupBy”过滤器。我们的 ng-repeat 接收到 (key, values[]) 的组合,我们可以在接下来的迭代中使用。

      2014 年 6 月 11 日更新 我通过过滤器扩展了组以考虑使用表达式作为键(例如嵌套变量)。角度解析服务非常方便:

      过滤器(支持表达式)

      app.filter('groupBy', function($parse) {
          return _.memoize(function(items, field) {
              var getter = $parse(field);
              return _.groupBy(items, function(item) {
                  return getter(item);
              });
          });
      });
      

      控制器(带有嵌套对象)

      app.controller('homeCtrl', function($scope) {
          var teamAlpha = {name: 'team alpha'};
          var teamBeta = {name: 'team beta'};
          var teamGamma = {name: 'team gamma'};
      
          $scope.teamPlayers = [{name: 'Gene', team: teamAlpha},
                            {name: 'George', team: teamBeta},
                            {name: 'Steve', team: teamGamma},
                            {name: 'Paula', team: teamBeta},
                            {name: 'Scruath of the 5th sector', team: teamGamma}];
      });
      

      html(带有 sortBy 表达式)

      <li ng-repeat="(team, players) in teamPlayers | groupBy:'team.name'">
          {{team}}
          <ul>
              <li ng-repeat="player in players">
                  {{player.name}}
              </li>
          </ul>
      </li>
      

      JSFiddle: http://jsfiddle.net/k7fgB/2/

      【讨论】:

      • 有一点需要注意——默认情况下,memoize 使用第一个参数(即“items”)作为缓存键——所以如果你传递相同的“items”和不同的“字段”它将返回相同的缓存值。欢迎提供解决方案。
      • 我认为你可以使用 $id 值来解决这个问题:item in items track by $id(item)
      • 为什么下划线不是 nesc 时包含下划线作为 dep
      • @TomCarver 您可以使用解析器函数作为 _.memoize 的第二个参数来创建包含“字段”的缓存键。这个函数让你定义你自己的缓存键。例如_.memoize( ... , function (items, field) { return _.pluck(items,'name').toString() + '-' + field})。这个 SO 答案有更多细节:stackoverflow.com/a/16513030/490592
      【解决方案5】:

      你可以使用angular.filter模块的groupBy。
      所以你可以这样做:

      JS:

      $scope.players = [
        {name: 'Gene', team: 'alpha'},
        {name: 'George', team: 'beta'},
        {name: 'Steve', team: 'gamma'},
        {name: 'Paula', team: 'beta'},
        {name: 'Scruath', team: 'gamma'}
      ];
      

      HTML:

      <ul ng-repeat="(key, value) in players | groupBy: 'team'">
        Group name: {{ key }}
        <li ng-repeat="player in value">
          player: {{ player.name }} 
        </li>
      </ul>
      

      结果:
      组名:alpha
      * 玩家:吉恩
      组名:beta
      * 球员:乔治
      * 球员:宝拉
      组名:伽玛
      * 球员:史蒂夫
      * 玩家:斯克鲁斯

      更新: jsbin 记住使用angular.filter 的基本要求,特别注意您必须将其添加到模块的依赖项中:

      (1) 您可以使用 4 种不同的方法安装 angular-filter:

      1. 克隆并构建此存储库
      2. 通过 Bower:从终端运行 $ bower install angular-filter
      3. 通过 npm:从终端运行 $ npm install angular-filter
      4. 通过cdnjs http://www.cdnjs.com/libraries/angular-filter

      (2) 在包含 Angular 本身之后,在 index.html 中包含 angular-filter.js(或 angular-filter.min.js)。

      (3) 将“angular.filter”添加到主模块的依赖项列表中。

      【讨论】:

      • 很好的例子。但是,key 返回的是组名,而不是实际的 key……我们如何解决这个问题?
      • 不要忘记包含angular.filter 模块。
      • 你可以使用 order-by 和 group-by @erfling, PTAL on: github.com/a8m/angular-filter/wiki/…
      • 哇哦。谢谢。我没想到订购嵌套循环会以这种方式影响外部循环。这真的很有用。 +1
      • @Xyroid 即使我正在寻找相同的东西,我想将 key 作为对象。祝你好运
      【解决方案6】:

      更新

      我最初写这个答案是因为 Ariel M.when combined with other $filters 建议的旧版本解决方案触发了"Infite $diggest Loop Error" (infdig)。幸运的是这个问题已经在angular.filter的最新版本中得到了解决。

      我建议了以下实现,that didn't have that issue

      angular.module("sbrpr.filters", [])
      .filter('groupBy', function () {
        var results={};
          return function (data, key) {
              if (!(data && key)) return;
              var result;
              if(!this.$id){
                  result={};
              }else{
                  var scopeId = this.$id;
                  if(!results[scopeId]){
                      results[scopeId]={};
                      this.$on("$destroy", function() {
                          delete results[scopeId];
                      });
                  }
                  result = results[scopeId];
              }
      
              for(var groupKey in result)
                result[groupKey].splice(0,result[groupKey].length);
      
              for (var i=0; i<data.length; i++) {
                  if (!result[data[i][key]])
                      result[data[i][key]]=[];
                  result[data[i][key]].push(data[i]);
              }
      
              var keys = Object.keys(result);
              for(var k=0; k<keys.length; k++){
                if(result[keys[k]].length===0)
                  delete result[keys[k]];
              }
              return result;
          };
      });
      

      但是,此实现仅适用于 Angular 1.3 之前的版本。 (我会尽快更新这个答案,提供适用于所有版本的解决方案。)

      我其实是wrote a post about the steps that I took to develop this $filter, the problems that I encountered and the things that I learned from it

      【讨论】:

      • 嗨@Josep,看看新的angular-filter 版本 - 0.5.0,没有更多的例外。 groupBy 可以与任何过滤器链接。另外,您的测试用例已成功完成 - 这是plunker 谢谢。
      • @Josep 在 Angular 1.3 中遇到问题
      【解决方案7】:

      除了接受的答案之外,如果您想按多列进行分组,您还可以使用它:

      <ul ng-repeat="(key, value) in players | groupBy: '[team,name]'">
      

      【讨论】:

        【解决方案8】:

        如果您在 js 代码中需要它。您可以使用 angula-filter lib 的注入方法。 像这样。

        function controller($scope, $http, groupByFilter) {     
        
           var groupedData = groupByFilter(originalArray, 'groupPropName');
        
        }
        

        https://github.com/a8m/angular-filter/wiki/Common-Questions#inject-filters

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-01-25
          • 2019-07-21
          • 2021-11-28
          相关资源
          最近更新 更多