如何使用角度过滤器对数据进行分组?


136

我有一个属于每个小组的球员名单。如何使用过滤器列出每个组的用户?

[{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'}];

我正在寻找这个结果:

  • 团队Alpha
    • 基因
  • 团队Beta
    • 乔治
    • 宝拉
  • 团队伽玛
    • 史蒂夫
    • 第五部门的启示

Answers:


182

您可以使用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
*播放器:Gene
组名:beta
*播放器:George
*播放器:Paula
组名:gamma
*播放器:Steve
*播放器:Scruath

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

(1)您可以使用4种不同的方法安装角度过滤器:

  1. 克隆并构建此存储库
  2. 通过Bower:通过运行$ bower在终端上安装angular-filter
  3. 通过npm:通过运行$ npm从您的终端安装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”添加到主模块的依赖项列表中。


很好的例子。但是,该键返回组名而不是实际的键...我们如何解决呢?
JohnAndrews

7
不要忘记包括该angular.filter模块。
2015年

1
您可以在github.com/a8m/angular-filter/wiki/…上使用group-by @erfling和PTAL进行排序
a8m

1
哇哦 谢谢。我没想到订购嵌套循环会以这种方式影响外部循环。这真的很有用。+1
7

1
@Xyroid甚至我正在寻找要key作为对象的对象。您身边的任何运气
太酷了

25

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

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

过滤器

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

注意“记忆”调用。此下划线方法会缓存函数的结果,并每次都停止对角度表达式进行求值,从而防止角度达到摘要迭代限制。

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>

我们将'groupBy'过滤器应用于'team'属性的teamPlayers范围变量。我们的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/


没错,小提琴链接已更新。谢谢你通知我
克里斯夫2014年

3
这实际上很整洁!最少的代码。
Benny Bottema 2014年

3
需要注意的一件事–默认情况下,memoize使用第一个参数(即“ items”)作为缓存键-因此,如果将相同的“ items”传递给不同的“ field”,它将返回相同的缓存值。欢迎解决方案。
汤姆·卡佛

我认为您可以使用$ id值解决此问题:$ id(item)中项目跟踪的项目
Caspar Harmer 2014年

2
哪些“可接受的答案”?在堆栈溢出时,只能有一个可接受的答案。
塞巴斯蒂安·马赫

19

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

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;
    }
}

很简单。不错的@Plantface。
杰夫·耶茨

只是辉煌。但是如果我想在单击时将新对象推送到$ scope.players怎么办?当您正在遍历一个函数时,它将被添加吗?
超级酷

16

我最初使用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);

});

1
这个答案不适用于AngularJS> 1.1,因为Promised不再用于数组。参见移民说明
Benny Bottema 2014年

6
此解决方案不需要Promise,因为您无需异步进行任何操作。在这种情况下,您只需跳过该步骤(jsFiddle)。
Benny Bottema 2014年

11

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

如果您想看到它的实现,这是小提琴

以下是指令:

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>

11

更新资料

我最初写这个答案是因为Ariel M.建议的解决方案的旧版本与其他$filters 结合使用时会触发Infite $ diggest Loop Error ”(infdig。幸运的是,此问题已在最新版本的angular.filter中解决

我建议以下实现,但不存在该问题

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之前的版本。(我将很快更新此答案,以提供适用于所有版本的解决方案。)

实际上,我已经写了一篇文章,介绍了我开发该产品所采取的步骤,$filter遇到的问题以及从中学到的东西


@Josep,您好,看看新angular-filter版本-0.5.0,没有更多例外。groupBy可以与任何过滤器连成一体。此外,您的测试用例成功完成- 非常 感谢。
2014年

1
@Josep在Angular 1.3中遇到问题
amcdnl 2015年

2

除了要接受的答案之外,如果要按多列分组还可以使用以下方法:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.