Come posso raggruppare i dati con un filtro angolare?


136

Ho un elenco di giocatori che appartengono a un gruppo ciascuno. Come posso usare un filtro per elencare gli utenti per gruppo?

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

Sto cercando questo risultato:

  • squadra alfa
    • Gene
  • team beta
    • Giorgio
    • Paula
  • squadra gamma
    • Steve
    • Scruath del 5 ° settore

Risposte:


182

È possibile utilizzare groupBy del modulo angular.filter .
così puoi fare qualcosa del genere:

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>

RISULTATO:
Nome gruppo: alpha
* giocatore: Gene
Nome gruppo: beta
* giocatore: George
* giocatore: Paula
Nome del gruppo: gamma
* giocatore: Steve
* giocatore: Scruath

AGGIORNARE: jsbin Ricorda i requisiti di base da usare angular.filter, in particolare nota che devi aggiungerlo alle dipendenze del tuo modulo:

(1) È possibile installare il filtro angolare utilizzando 4 metodi diversi:

  1. clonare e creare questo repository
  2. via Bower: eseguendo $ bower installa il filtro angolare dal tuo terminale
  3. via npm: eseguendo $ npm installa il filtro angolare dal tuo terminale
  4. tramite cdnjs http://www.cdnjs.com/libraries/angular-filter

(2) Includi angular-filter.js (o angular-filter.min.js) nel tuo index.html, dopo aver incluso Angular stesso.

(3) Aggiungi 'angular.filter' all'elenco delle dipendenze del tuo modulo principale.


Ottimo esempio Tuttavia, la chiave restituisce il nome del gruppo e non la chiave effettiva ... come possiamo risolverlo?
John Andrew

7
Non dimenticare di includere il angular.filtermodulo.
Puce,

1
puoi usare order-by con group-by @erfling, PTAL su: github.com/a8m/angular-filter/wiki/…
a8m

1
Oh wow Grazie. Non mi aspettavo che l'ordinamento del ciclo nidificato influisse su quello esterno in quel modo. Questo è davvero utile. +1
eruzione il

1
@Xyroid anche io sto cercando lo stesso che voglio fare keycome oggetto. un
po

25

Oltre alle risposte accettate sopra, ho creato un filtro generico "groupBy" utilizzando la libreria underscore.js.

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

Il filtro

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

Nota la chiamata 'memoize'. Questo metodo di sottolineatura memorizza nella cache il risultato della funzione e impedisce che angolare valuti ogni volta l'espressione del filtro, evitando così che angolare raggiunga il limite di iterazioni digest.

L'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>

Applichiamo il nostro filtro "groupBy" sulla variabile dell'ambito teamPlayers, sulla proprietà "team". La nostra ng-repeat riceve una combinazione di (chiave, valori []) che possiamo usare nelle seguenti iterazioni.

Aggiornamento dell'11 giugno 2014 Ho ampliato il gruppo per filtro per tenere conto dell'uso delle espressioni come chiave (ad esempio variabili nidificate). Il servizio di analisi angolare è molto utile per questo:

Il filtro (con supporto per le espressioni)

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

Il controller (con oggetti nidificati)

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

L'html (con espressione 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/


Hai ragione, il collegamento del violino è stato aggiornato. Grazie per avermi avvisato.
chrisv,

3
Questo è abbastanza pulito in realtà! Minima quantità di codice.
Benny Bottema,

3
una cosa da notare con questo - per impostazione predefinita memoize utilizza il primo parametro (cioè "elementi") come chiave della cache - quindi se si passa gli stessi "elementi" con un "campo" diverso, verrà restituito lo stesso valore memorizzato nella cache. Soluzioni benvenute.
Tom Carver,

Penso che tu possa usare il valore $ id per aggirare questo: oggetto nella traccia degli oggetti di $ id (oggetto)
Caspar Harmer

2
Quali "risposte accettate"? Su Stack Overflow, può esserci una sola risposta accettata.
Sebastian Mach,

19

Prima fai un loop usando un filtro che restituirà solo squadre uniche, quindi un loop nidificato che restituisce tutti i giocatori per squadra corrente:

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>

script:

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

Così semplice. Nice one @Plantface.
Jeff Yates,

semplicemente geniale. ma cosa succede se voglio spingere un nuovo oggetto in $ scope.players al clic? come si esegue il ciclo attraverso una funzione verrà aggiunta?
Super cool

16

Inizialmente ho usato la risposta di Plantface, ma non mi piaceva l'aspetto della sintassi nella mia visione.

L'ho rielaborato per utilizzare $ q.defer per postelaborare i dati e restituire un elenco su team univoci, che viene quindi utilizzato come filtro.

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

Visualizza

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

controllore

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
Questa risposta non funziona con AngularJS> 1.1 poiché Promised non viene più scartato per gli array. Vedi le note sull'immigrazione
Benny Bottema,

6
Non c'è bisogno della Promessa in questa soluzione, poiché non stai facendo nulla in modo asincrono. In questo caso, puoi semplicemente saltare quel passaggio ( jsFiddle ).
Benny Bottema,

11

Entrambe le risposte erano buone, quindi le ho spostate in una direttiva in modo che sia riutilizzabile e non sia necessario definire una seconda variabile di ambito.

Ecco il violino se vuoi vederlo implementato

Di seguito è la direttiva:

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

Quindi può essere utilizzato come segue:

<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

Aggiornare

Inizialmente ho scritto questa risposta perché la vecchia versione della soluzione suggerita da Ariel M. quando combinato con altri $filters ha innescato un " Infite $ diggest Loop Error " ( infdig) . Fortunatamente questo problema è stato risolto nell'ultima versione di angular.filter .

Ho suggerito la seguente implementazione, che non aveva questo problema :

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

Tuttavia, questa implementazione funzionerà solo con versioni precedenti a Angular 1.3. (Aggiornerò questa risposta a breve fornendo una soluzione che funziona con tutte le versioni.)

In realtà ho scritto un post sui passi che ho preso per sviluppare questo $filter, i problemi che ho incontrato e le cose che ho imparato da esso .


Ciao @Josep, dai un'occhiata alla nuova angular-filterversione - 0.5.0, non ci sono più eccezioni. groupBypuò essere catena con qualsiasi filtro. inoltre, sei un ottimo caso di test che finiscono con successo - ecco un plunker Grazie.
8

1
@Josep Avere problemi in Angular 1.3
amcdnl,

2

Oltre alla risposta accettata, puoi utilizzare questa opzione se desideri raggruppare per più colonne :

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.