7 votes

Les "headers" d'AngularJS dans ng-repeat

J'ai un problème qui s'est présenté plus d'une fois ces derniers temps, et je me demande quelle est la meilleure façon de procéder. Pour faire simple :

J'ai des données affichées dans un ng-repeat, triées par un élément particulier. Disons qu'elles sont triées par Nom, par exemple. Mon objectif est d'avoir des en-têtes aux coupures de lettres dans la liste alphabétique :

----A----
Abe Lincoln
Adam Smith
----B----
Barack Obama
Barry Zuckercorn
----C----
...

et ainsi de suite.

Les choses que j'ai essayées sont les suivantes :

  • Le contrôleur doit reconstruire entièrement les données du modèle au fur et à mesure qu'elles arrivent, en les plaçant manuellement dans un tableau de groupes de lettres. Par exemple, mon service a un tableau de "posts" et mon contrôleur, au fur et à mesure que le service se met à jour, mélange manuellement ces "posts" dans un tableau de "letterGroups". Il est alors possible d'avoir simplement deux ng-repeats imbriqués

Cependant, cette méthode repose sur une manipulation importante des données, et il n'est pas facile de modifier à la volée l'ordre de tri des données.

  • Lorsque le modèle est mis à jour, le contrôleur doit "doper" les données en les parcourant manuellement, en vérifiant si la lettre change et en ajoutant une propriété "header" à cet élément ( post.header = true ). Le ng-repeat peut alors vérifier si l'élément sur lequel il se trouve est un en-tête, et utiliser ng-if pour insérer quelque chose d'autre dans le DOM.

Cela semble un peu plus propre, mais en raison de la façon dont ng-repeat fonctionne, l'élément d'en-tête doit être "inclus" au même niveau que l'élément ng-repeating. Par exemple, si vous faites le ng-repeat sur <tr> cela signifierait qu'il serait impossible d'insérer une autre <tr> pour l'en-tête, puisque l'élément spécial doit apparaître à l'intérieur el <tr>

Enfin, dans le même ordre d'idées que ci-dessus :

  • Demander au contrôleur de maintenir une liste d'"index de clés" - cette méthode présente l'avantage de ne pas modifier les données comme la deuxième méthode ci-dessus, mais fonctionne généralement de la même manière.

EDIT :

J'ai écrit un article de blog aquí expliquant comment j'ai finalement géré ce problème - grâce à la discussion Google Groups liée à la réponse de Ian ci-dessous.

5voto

Ian Mercer Points 19271

Créer un filtre Angular. Il peut accepter la liste triée, la diviser par première lettre et retourner un objet pour chaque morceau avec la lettre et un tableau d'objets qui commencent par cette lettre.

Voir par exemple le filtre par taille ici . Notez en particulier la discussion autour de la création de valeurs de hachage pour vos valeurs de morceaux.

1voto

Felix Points 108

J'ai écrit une petite directive qui observe les objets et ajoute automatiquement une propriété $header au premier de la liste. Utilisez-la comme suit :

 <div ng-repeat="item in data.items | orderBy:... | filter:... as filteredItems" header-list objects="filteredItems" header="getHeader">
    <div ng-show="item.$header">{{item.$header}}</div>
    <div>Your normal content</div>
 </div>

Vous pouvez passer une fonction qui construira l'en-tête comme vous le souhaitez. La fonction peut être une méthode d'instance ou une fonction dans votre champ d'application. Pour obtenir la première lettre, définissez dans votre contrôleur.

$scope.getHeader = function(item) {
  return item.name.slice(0, 1);
}, 

La liste sera automatiquement mise à jour lorsque les filtres et les ordres seront modifiés.

app.module("...").directive('headerList', function () {
  return {
    restrict: 'A',
    scope: {
      objects: '=',
      header: '=' // This must be a function or instance method
    },
    link: function(scope) {
      scope.$watch('objects', function() {

        var lastHeader, currentHeader;

        scope.objects.forEach(function (obj) {
          // We pass obj twice, so it can be used is instance method or regular function that takes the object as argument.
          currentHeader = scope.header.call(obj, obj);

          // in order to display a header per type, we mark the first event of each kind as header
          if (currentHeader !== lastHeader) {
            obj.$header = currentHeader;
            lastHeader = currentHeader;
          } else {
            obj.$header = null;
          }
        });

      });
    }
  }
});

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