Wie kann ich Daten mit einem Winkelfilter gruppieren?

136

Ich habe eine Liste von Spielern, die jeweils zu einer Gruppe gehören. Wie kann ich einen Filter verwenden, um die Benutzer pro Gruppe aufzulisten?

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

Ich suche dieses Ergebnis:

  • Team Alpha
    • Gen
  • Team Beta
    • George
    • Paula
  • Team Gamma
    • Steve
    • Scruath des 5. Sektors
Benny Bottema
quelle

Antworten:

182

Sie können groupBy des Angular.filter- Moduls verwenden.
Sie können also so etwas tun:

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>

ERGEBNIS: Gruppenname:
Alpha
* Spieler: Gen Gruppenname
: Beta
* Spieler: George
* Spieler: Paula Gruppenname
: Gamma
* Spieler: Steve
* Spieler: Scruath

UPDATE: jsbin Beachten Sie die grundlegenden Anforderungen, die verwendet werden angular.filtermüssen. Beachten Sie insbesondere, dass Sie diese zu den Abhängigkeiten Ihres Moduls hinzufügen müssen:

(1) Sie können den Winkelfilter mit 4 verschiedenen Methoden installieren:

  1. Klonen und erstellen Sie dieses Repository
  2. via Bower: Wenn Sie $ bower ausführen, installieren Sie den Winkelfilter von Ihrem Terminal aus
  3. via npm: Wenn Sie $ npm ausführen, installieren Sie den Winkelfilter von Ihrem Terminal
  4. über cdnjs http://www.cdnjs.com/libraries/angular-filter

(2) Fügen Sie angular-filter.js (oder angle-filter.min.js) in Ihre index.html ein, nachdem Sie Angular selbst eingefügt haben.

(3) Fügen Sie 'Angular.filter' zur Abhängigkeitsliste Ihres Hauptmoduls hinzu.

a8m
quelle
Tolles Beispiel. Der Schlüssel gibt jedoch den Gruppennamen und nicht den tatsächlichen Schlüssel zurück ... wie können wir das lösen?
John Andrews
7
Vergessen Sie nicht, das angular.filterModul beizufügen.
Puce
1
Sie können order-by mit group-by @erfling, PTAL unter folgender Adresse verwenden: github.com/a8m/angular-filter/wiki/…
a8m
1
Oh wow. Vielen Dank. Ich hatte nicht erwartet, dass die Anordnung der verschachtelten Schleife die äußere auf diese Weise beeinflusst. Das ist wirklich nützlich. +1
Erfling
1
@Xyroid sogar ich suche das gleiche, das ich keyals Objekt machen möchte . Viel Glück von Ihrer Seite
super cool
25

Zusätzlich zu den oben akzeptierten Antworten habe ich mithilfe der Bibliothek underscore.js einen generischen 'groupBy'-Filter erstellt.

JSFiddle (aktualisiert): http://jsfiddle.net/TD7t3/

Der Filter

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

Beachten Sie den Aufruf 'merken'. Diese Unterstrichmethode speichert das Ergebnis der Funktion im Cache und verhindert, dass Angular den Filterausdruck jedes Mal auswertet, wodurch verhindert wird, dass Angular die Digest-Iterationsgrenze erreicht.

Das 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>

Wir wenden unseren Filter 'groupBy' auf die Bereichsvariable teamPlayers in der Eigenschaft 'team' an. Unsere ng-Wiederholung erhält eine Kombination von (Schlüssel, Werte []), die wir in unseren folgenden Iterationen verwenden können.

Update 11. Juni 2014 Ich habe die Gruppe nach Filter erweitert, um die Verwendung von Ausdrücken als Schlüssel (z. B. verschachtelte Variablen) zu berücksichtigen. Der eckige Analyseservice ist hierfür sehr praktisch:

Der Filter (mit Ausdrucksunterstützung)

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

Der Controller (mit verschachtelten Objekten)

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

Das HTML (mit sortBy Ausdruck)

<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
quelle
Sie haben Recht, Geigenlink wurde aktualisiert. Danke, dass Sie mich benachrichtigt haben.
Chrisv
3
Das ist eigentlich ziemlich ordentlich! Am wenigsten Code.
Benny Bottema
3
Beachten Sie dabei Folgendes: Standardmäßig verwendet memoize den ersten Parameter (dh "Elemente") als Cache-Schlüssel. Wenn Sie also dieselben "Elemente" mit einem anderen "Feld" übergeben, wird der gleiche zwischengespeicherte Wert zurückgegeben. Lösungen willkommen.
Tom Carver
Ich denke, Sie können den Wert $ id verwenden, um dies
Caspar Harmer
2
Welche "akzeptierten Antworten"? Beim Stapelüberlauf kann nur eine Antwort akzeptiert werden.
Sebastian Mach
19

Machen Sie zuerst eine Schleife mit einem Filter, der nur eindeutige Teams zurückgibt, und dann eine verschachtelte Schleife, die alle Spieler pro aktuellem Team zurückgibt:

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>

Skript:

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;
    }
}
Benny Bottema
quelle
So einfach. Schöne @Plantface.
Jeff Yates
einfach brilliant . aber was ist, wenn ich ein neues Objekt auf $ scope.players per Klick verschieben möchte? Wenn Sie eine Funktion durchlaufen, wird sie hinzugefügt?
Super cool
16

Ich habe ursprünglich die Antwort von Plantface verwendet, aber mir hat nicht gefallen, wie die Syntax aus meiner Sicht aussah.

Ich habe es überarbeitet, um mit $ q.defer die Daten nachzubearbeiten und eine Liste mit eindeutigen Teams zurückzugeben, die dann als Filter verwendet wird.

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

Aussicht

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

Regler

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

});
Walter Stabosz
quelle
1
Diese Antwort funktioniert nicht mit AngularJS> 1.1, da Promised nicht mehr für Arrays ausgepackt werden. Siehe die Einwanderungsnotizen
Benny Bottema
6
In dieser Lösung ist das Versprechen nicht erforderlich, da Sie nichts asynchron ausführen. In diesem Fall können Sie diesen Schritt einfach überspringen ( jsFiddle ).
Benny Bottema
11

Beide Antworten waren gut, deshalb habe ich sie in eine Direktive verschoben, damit sie wiederverwendbar ist und keine zweite Bereichsvariable definiert werden muss.

Hier ist die Geige, wenn Sie möchten, dass sie implementiert wird

Unten ist die Richtlinie:

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

Dann kann es wie folgt verwendet werden:

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

Aktualisieren

Ich habe diese Antwort ursprünglich geschrieben, weil die alte Version der von Ariel M. vorgeschlagenen Lösung in Kombination mit anderen $filters einen " Infite $ diggest Loop Error " ( infdig) auslöste . Glücklicherweise wurde dieses Problem in der neuesten Version des Angular.Filters behoben .

Ich schlug die folgende Implementierung vor, die dieses Problem nicht hatte :

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

Diese Implementierung funktioniert jedoch nur mit Versionen vor Angular 1.3. (Ich werde diese Antwort in Kürze aktualisieren und eine Lösung bereitstellen, die mit allen Versionen funktioniert.)

Ich habe tatsächlich einen Beitrag über die Schritte geschrieben, die ich unternommen habe, um dies zu entwickeln $filter, die Probleme, auf die ich gestoßen bin, und die Dinge, die ich daraus gelernt habe .

Josep
quelle
Hallo @Josep, schau dir die neue angular-filterVersion an - 0.5.0, es gibt keine Ausnahme mehr. groupBykann mit jedem Filter verkettet werden. Außerdem sind Sie großartige Testfälle, die erfolgreich abgeschlossen wurden - hier ist ein Plunker. Danke.
a8m
1
@ Josephp Probleme in Angular 1.3
amcdnl
2

Zusätzlich zur akzeptierten Antwort können Sie diese verwenden, wenn Sie nach mehreren Spalten gruppieren möchten :

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