140 votes

Comment regrouper des données avec un filtre Angular ?

J'ai une liste de joueurs qui appartiennent chacun à un groupe. Comment puis-je utiliser un filtre pour lister les utilisateurs par groupe ?

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

Je cherche ce résultat :

  • équipe alpha
    • Gene
  • équipe bêta
    • George
    • Paula
  • équipe gamma
    • Steve
    • Scruath du 5ème secteur

183voto

Ariel M. Points 141

Vous pouvez utiliser groupBy de filtre angulaire module.
donc vous pouvez faire quelque chose comme ça :

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>

RÉSULTAT :
Nom du groupe : alpha
* joueur : Gene
Nom du groupe : beta
* joueur : George
* joueur : Paula
Nom du groupe : gamma
* joueur : Steve
* joueur : Scruath

UPDATE : jsbin Rappelez-vous les conditions de base pour utiliser angular.filter Vous devez l'ajouter aux dépendances de votre module :

(1) Vous pouvez installer angular-filter en utilisant 4 méthodes différentes :

  1. cloner et construire ce dépôt
  2. via Bower : en exécutant $ bower install angular-filter depuis votre terminal
  3. via npm : en exécutant $ npm install angular-filter depuis votre terminal
  4. via cdnjs http://www.cdnjs.com/libraries/angular-filter

(2) Incluez angular-filter.js (ou angular-filter.min.js) dans votre index.html, après avoir inclus Angular lui-même.

(3) Ajoutez "angular.filter" à la liste des dépendances de votre module principal.

0 votes

Un bon exemple. Cependant, la clé renvoie le nom du groupe et non la clé réelle... comment résoudre ce problème ?

0 votes

Quelle est la "clé réelle" pour vous ? Pouvez-vous fournir un exemple de fiddle/jsbin ?

0 votes

J'essaie de comprendre comment ajouter une case à cocher pour un groupe et des éléments, si la case à cocher du nom du groupe est cliquée, tous les éléments qui se trouvent sous le groupe doivent être sélectionnés.

25voto

chrisv Points 116

En plus des réponses acceptées ci-dessus, j'ai créé un filtre générique "groupBy" en utilisant la bibliothèque underscore.js.

JSFiddle (mis à jour) : http://jsfiddle.net/TD7t3/

Le filtre

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

Notez l'appel "memoize". Cette méthode de soulignement met en cache le résultat de la fonction et empêche angular d'évaluer l'expression du filtre à chaque fois, ce qui l'empêche d'atteindre la limite d'itérations du digest.

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

Nous appliquons notre filtre 'groupBy' sur la variable de portée teamPlayers, sur la propriété 'team'. Notre ng-repeat reçoit une combinaison de (key, values[]) que nous pouvons utiliser dans nos itérations suivantes.

Mise à jour du 11 juin 2014 J'ai étendu le filtre "group by" pour tenir compte de l'utilisation d'expressions comme clé (par exemple, des variables imbriquées). Le service angular parse est très pratique pour cela :

Le filtre (avec support d'expression)

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

Le contrôleur (avec des objets imbriqués)

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

Le html (avec l'expression 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/

3 votes

Une chose à noter - par défaut memoize utilise le premier paramètre (i.e. 'items') comme clé de cache - donc si vous lui passez le même 'items' avec un 'field' différent il retournera la même valeur en cache. Solutions bienvenues.

0 votes

Je pense que vous pouvez utiliser la valeur $id pour contourner ce problème : item dans items track by $id(item)

0 votes

Pourquoi inclure l'underscore en tant que dep quand il n'est pas nesc

19voto

Plantface Points 863

Faites d'abord une boucle en utilisant un filtre qui renvoie uniquement les équipes uniques, puis une boucle imbriquée qui renvoie tous les joueurs par équipe actuelle :

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

0 votes

Mais que faire si je veux pousser un nouvel objet dans $scope.players au moment du clic ? Comme vous bouclez à travers une fonction, sera-t-il ajouté ?

16voto

Walter Stabosz Points 3012

J'avais initialement utilisé la réponse de Plantface, mais je n'aimais pas l'aspect de la syntaxe dans ma vue.

Je l'ai retravaillé pour utiliser $q.defer pour post-traiter les données et renvoyer une liste d'équipes uniques, qui est ensuite utilisée comme filtre.

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

Voir

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

Contrôleur

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 votes

Cette réponse ne fonctionne pas avec AngularJS > 1.1 car les promesses ne sont plus déballées pour les tableaux. Voir le notes sur l'immigration

6 votes

Il n'y a pas besoin de Promise dans cette solution, car vous ne faites rien de manière asynchrone. Dans ce cas, vous pouvez simplement sauter cette étape ( jsFiddle ).

11voto

Theo Points 550

Les deux réponses étaient bonnes et je les ai donc déplacées dans une directive afin qu'elles soient réutilisables et qu'il ne soit pas nécessaire de définir une deuxième variable de portée.

Voici le violon si vous voulez le voir mis en œuvre

Voici la directive :

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

Il peut alors être utilisé comme suit :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X