Angularフィルターを使用してデータをグループ化するにはどうすればよいですか?


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

私はこの結果を探しています:

  • チームアルファ
    • 遺伝子
  • チームベータ
    • ジョージ
    • ポーラ
  • チームガンマ
    • スティーブ
    • 第5セクターのスクラース

回答:


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
* player:Gene
グループ名:beta
* player:George
* player:Paula
グループ名:gamma
* player:Steve
* player:Scruath

更新: jsbinを使用するための基本的な要件を覚えておいてくださいangular.filter。特に、モジュールの依存関係に追加する必要があることに注意してください。

(1)4つの異なる方法を使用して角度フィルターをインストールできます。

  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」を追加します。


素晴らしい例です。しかし、キーは実際のキーではなくグループ名を返します...それをどのように解決できますか?
JohnAndrews

7
angular.filterモジュールを含めることを忘れないでください。
Puce

1
:あなたは、オーダー別にグループごとに、PTALを@erflingで使用することができますgithub.com/a8m/angular-filter/wiki/...
a8m

1
ああすごい。ありがとう。ネストされたループの順序がそのように外側のループに影響を与えるとは思いませんでした。それは本当に便利です。+1
2016年

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

「メモ」呼び出しに注意してください。このアンダースコアメソッドは、関数の結果をキャッシュし、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>

「group」フィルターをteamPlayersスコープ変数の「team」プロパティに適用します。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/


そうです、フィドルリンクが更新されました。お知らせありがとうございます。
chrisv 2014年

3
これは実際にはかなりきれいです!最小限のコード。
ベニーボッテマ2014

3
これに関する注意点の1つ-デフォルトでは、memoizeは最初のパラメーター(つまり、「items」)をキャッシュキーとして使用します。したがって、異なる「フィールド」で同じ「items」を渡すと、同じキャッシュ値が返されます。ソリューションを歓迎します。
トム・カーバー

$ id値を使用してこれを回避できると思います:$ id(item)による項目追跡の項目
Caspar Harmer '22

2
「受け入れられた答え」はどれですか?Stack Overflowでは、受け入れられる回答は1つだけです。
セバスチャンマッハ

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にプッシュしたい場合はどうなりますか?uが関数をループしているので、追加されますか?
スーパークール

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が配列に対してアンラップされなくなったためです。移民のメモを
ベニーボッテマ2014

6
このソリューションでは、非同期で何も実行していないため、Promiseは必要ありません。この場合、そのステップをスキップできます(jsFiddle)。
ベニーボッテマ2014

11

どちらの回答も良かったので、ディレクティブに移動して、再利用可能にし、2番目のスコープ変数を定義する必要がないようにしました。

実装を確認したい場合のフィドルです

以下はディレクティブです。

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.によって提案された古いバージョンのソリューションを他$filterのと組み合わせると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

2

受け入れられた回答に加えて、複数の列でグループ化する場合は、これを使用できます

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.