Comment utiliser ng-repeat sans élément html

142

J'ai besoin d'utiliser ng-repeat(dans AngularJS) pour lister tous les éléments d'un tableau.

La complication est que chaque élément du tableau se transforme en une, deux ou trois lignes d'une table.

Je ne peux pas créer de code HTML valide, si ng-repeatest utilisé sur un élément, car aucun type d'élément répétitif n'est autorisé entre <tbody>et <tr>.

Par exemple, si j'utilisais ng-repeat sur <span>, j'obtiendrais:

<table>
  <tbody>
    <span>
      <tr>...</tr>
    </span>
    <span>
      <tr>...</tr>
      <tr>...</tr>
      <tr>...</tr>
    </span>
    <span>
      <tr>...</tr>
      <tr>...</tr>
    </span>
  </tbody>
</table>          

Ce qui est du HTML invalide.

Mais ce dont j'ai besoin pour être généré, c'est:

<table>
  <tbody>
    <tr>...</tr>
    <tr>...</tr>
    <tr>...</tr>
    <tr>...</tr>
    <tr>...</tr>
    <tr>...</tr>
  </tbody>
</table>          

où la première ligne a été générée par le premier élément du tableau, les trois suivants par le deuxième et le cinquième et le sixième par le dernier élément du tableau.

Comment puis-je utiliser ng-repeat de telle sorte que l'élément html auquel il est lié «disparaisse» lors du rendu?

Ou y a-t-il une autre solution à cela?


Clarification: la structure générée doit ressembler à celle ci-dessous. Chaque élément du tableau peut générer entre 1 et 3 lignes du tableau. La réponse devrait idéalement prendre en charge 0-n lignes par élément de tableau.

<table>
  <tbody>
    <!-- array element 0 -->
    <tr>
      <td>One row item</td>
    </tr>
    <!-- array element 1 -->
    <tr>
      <td>Three row item</td>
    </tr>
    <tr>
      <td>Some product details</td>
    </tr>
    <tr>
      <td>Customer ratings</td>
    </tr>
    <!-- array element 2 -->
    <tr>
      <td>Two row item</td>
    </tr>
    <tr>
      <td>Full description</td>
    </tr>
  </tbody>
</table>          
abeille fanée
la source
Peut-être devriez-vous utiliser "replace: true"? Voir ceci: stackoverflow.com/questions/11426114/…
Aussi, pourquoi ne pouvez-vous pas utiliser ng-repeat sur le tr lui-même?
2
@Tommy, parce que "chaque élément du tableau se transformera en une, deux ou trois lignes d'une table". Si j'utilisais ng-repeat sur le tr, j'obtiendrais une ligne par élément de tableau, pour autant que je sache.
fadedbee
1
OK je vois. Ne pouvez-vous pas simplement aplatir le modèle avant de l'utiliser dans le répéteur?
@Tommy, non. Les 1-3 trs qui sont générés par un élément du tableau n'ont pas la même structure.
fadedbee

Réponses:

66

Mise à jour: si vous utilisez Angular 1.2+, utilisez ng-repeat-start . Voir la réponse de @ jmagnusson.

Sinon, que diriez-vous de mettre le ng-repeat sur tbody? (AFAIK, il est normal d'avoir plusieurs <tbody> dans une seule table.)

<tbody ng-repeat="row in array">
  <tr ng-repeat="item in row">
     <td>{{item}}</td>
  </tr>
</tbody>
Mark Rajcok
la source
62
Bien que cela puisse techniquement fonctionner, il est très décevant que la réponse à ce cas d'utilisation courant soit que vous devez injecter un balisage arbitraire (sinon inutile). J'ai le même problème (groupes répétés de lignes - un en-tête TR avec un ou plusieurs TR enfants, répétés en groupe). Trivial avec d'autres moteurs de template, hacky au mieux avec Angular semble-t-il.
Brian Moeskau
1
D'accord. J'essaye de faire une répétition sur les éléments DT + DD. il n'y a aucun moyen de faire cela sans ajouter un élément d'emballage non valide
David Lin
2
@DavidLin, dans Angular v1.2 (chaque fois qu'il sortira), vous pourrez répéter sur plusieurs éléments, par exemple, dt et dd: youtube.com/watch?v=W13qDdJDHp8&t=17m28s
Mark Rajcok
Ceci est géré avec élégance dans KnockoutJS en utilisant la syntaxe de flux de contrôle sans conteneur: knockoutjs.com/documentation/foreach-binding.html . J'espère voir Angular faire quelque chose de similaire bientôt.
Cory House
3
Cette réponse est obsolète, utilisez plutôt ng-repeat-start
iwein
73

Depuis AngularJS 1.2, il existe une directive appelée ng-repeat-startqui fait exactement ce que vous demandez. Voir ma réponse dans cette question pour une description de son utilisation.

jmagnusson
la source
Angular a rattrapé son retard, c'est la bonne solution maintenant.
iwein le
33

Si vous utilisez ng> 1.2, voici un exemple d'utilisation ng-repeat-start/endsans générer de balises inutiles:

<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
    <script>
      angular.module('mApp', []);
    </script>
  </head>
  <body ng-app="mApp">
    <table border="1" width="100%">
      <tr ng-if="0" ng-repeat-start="elem in [{k: 'A', v: ['a1','a2']}, {k: 'B', v: ['b1']}, {k: 'C', v: ['c1','c2','c3']}]"></tr>

      <tr>
        <td rowspan="{{elem.v.length}}">{{elem.k}}</td>
        <td>{{elem.v[0]}}</td>
      </tr>
      <tr ng-repeat="v in elem.v" ng-if="!$first">
        <td>{{v}}</td>
      </tr>

      <tr ng-if="0" ng-repeat-end></tr>
    </table>
  </body>
</html>

Le point important: pour les balises utilisées pour ng-repeat-startet ng-repeat-enddéfinies ng-if="0", ne pas laisser s'insérer dans la page. De cette façon, le contenu interne sera traité exactement comme dans knockoutjs (en utilisant les commandes in <!--...-->), et il n'y aura pas de déchets.

Duka Árpád
la source
après avoir défini ng-if = "0", il renvoie une erreur indiquant impossible de trouver ng-repeat-end correspondant.
vikky MCTS
19

Vous souhaiterez peut-être aplatir les données de votre contrôleur:

function MyCtrl ($scope) {
  $scope.myData = [[1,2,3], [4,5,6], [7,8,9]];
  $scope.flattened = function () {
    var flat = [];
    $scope.myData.forEach(function (item) {
      flat.concat(item);
    }
    return flat;
  }
}

Et puis dans le HTML:

<table>
  <tbody>
    <tr ng-repeat="item in flattened()"><td>{{item}}</td></tr>
  </tbody>
</table>
btford
la source
1
Non ce n'est pas. L'appel d'une fonction dans une expression ngRepeat n'est absolument pas recommandé
Nik
Dans mon cas, j'ai dû ajouter un séparateur pour chaque élément où l'index! = 0 et ng-repeat-start, ng-repeat-endcasser ma conception, donc la solution était de créer une variable supplémentaire en ajoutant cet objet séparateur avant chaque élément et d'itérer la nouvelle var: <div class="c477_group"> <div class="c477_group_item" ng-repeat="item in itemsWithSeparator" ng-switch="item.id" ng-class="{'-divider' : item.id == 'SEPARATOR'}"> <div class="c478" ng-switch-when="FAS"/> <div class="c478" ng-switch-when="SEPARATOR" /> <div class="c479" ng-switch-default /> </div> </div>
hermeslm
6

Ce qui précède est correct mais pour une réponse plus générale, cela ne suffit pas. J'avais besoin d'imbriquer ng-repeat, mais restez au même niveau html, c'est-à-dire écrire les éléments dans le même parent. Le tableau de balises contient des balises qui ont également un tableau de balises. C'est en fait un arbre.

[{ name:'name1', tags: [
  { name: 'name1_1', tags: []},
  { name: 'name1_2', tags: []}
  ]},
 { name:'name2', tags: [
  { name: 'name2_1', tags: []},
  { name: 'name2_2', tags: []}
  ]}
]

Voici donc ce que j'ai finalement fait.

<div ng-repeat-start="tag1 in tags" ng-if="false"></div>
    {{tag1}},
  <div ng-repeat-start="tag2 in tag1.tags" ng-if="false"></div>
    {{tag2}},
  <div ng-repeat-end ng-if="false"></div>
<div ng-repeat-end ng-if="false"></div>

Notez le ng-if = "false" qui masque les div de début et de fin.

Il devrait imprimer

nom1, nom1_1, nom1_2, nom2, nom2_1, nom2_2,

Αλέκος
la source
1

Je voudrais juste faire un commentaire, mais ma réputation fait encore défaut. Donc, j'ajoute une autre solution qui résout également le problème. Je voudrais vraiment réfuter l'affirmation de @bmoeskau selon laquelle la résolution de ce problème nécessite une solution de `` piratage au mieux '', et comme cela est apparu récemment dans une discussion, même si ce message a 2 ans, j'aimerais ajouter mon posséder deux cents:

Comme @btford l'a souligné, vous semblez essayer de transformer une structure récursive en liste, vous devez donc d'abord aplatir cette structure en liste. Sa solution fait cela, mais il y a une opinion selon laquelle l'appel de la fonction à l'intérieur du modèle est inélégant. si c'est vrai (honnêtement, je ne sais pas) cela ne nécessiterait-il pas simplement d'exécuter la fonction dans le contrôleur plutôt que la directive?

Dans tous les cas, votre html nécessite une liste, donc la portée qui le rend devrait avoir cette liste pour travailler avec. vous devez simplement aplatir la structure à l'intérieur de votre contrôleur. une fois que vous avez un tableau $ scope.rows, vous pouvez générer la table avec une seule et simple répétition ng. Pas de piratage, pas d'inélégance, simplement la façon dont il a été conçu pour fonctionner.

Les directives Angulars ne manquent pas de fonctionnalités. Ils vous obligent simplement à écrire du code HTML valide. Un de mes collègues a eu un problème similaire, citant @bmoeskau à l'appui des critiques sur les fonctionnalités de modélisation / rendu angulaires. En regardant le problème exact, il s'est avéré qu'il voulait simplement générer une balise ouverte, puis une balise de fermeture ailleurs, etc., tout comme au bon vieux temps où nous concattrions notre html à partir de chaînes ... non? non.

Quant à l'aplatissement de la structure en une liste, voici une autre solution:

// assume the following structure
var structure = [
    {
        name: 'item1', subitems: [
            {
                name: 'item2', subitems: [
                ],
            }
        ],
    }
];
var flattened = structure.reduce((function(prop,resultprop){
    var f = function(p,c,i,a){
        p.push(c[resultprop]);
        if (c[prop] && c[prop].length > 0 )
          p = c[prop].reduce(f,p);
        return p;
    }
    return f;
})('subitems','name'),[]);

// flattened now is a list: ['item1', 'item2']

cela fonctionnera pour toute structure arborescente comportant des sous-éléments. Si vous voulez l'élément entier au lieu d'une propriété, vous pouvez raccourcir encore plus la fonction d'aplatissement.

J'espère que cela pourra aider.

Ar Es
la source
Joli. Même si cela ne m'a pas aidé directement, cela m'a ouvert l'esprit à une autre solution. Merci beaucoup!
Marian Zburlea
0

pour une solution qui fonctionne vraiment

html

<remove  ng-repeat-start="itemGroup in Groups" ></remove>
   html stuff in here including inner repeating loops if you want
<remove  ng-repeat-end></remove>

ajouter une directive angular.js

//remove directive
(function(){
    var remove = function(){

        return {    
            restrict: "E",
            replace: true,
            link: function(scope, element, attrs, controller){
                element.replaceWith('<!--removed element-->');
            }
        };

    };
    var module = angular.module("app" );
    module.directive('remove', [remove]);
}());

pour une brève explication,

ng-repeat se lie à l' <remove>élément et boucle comme il se doit, et parce que nous avons utilisé ng-repeat-start / ng-repeat-end, il boucle un bloc de html pas seulement un élément.

puis la directive remove personnalisée place les <remove>éléments de début et de fin avec<!--removed element-->

Clint
la source
-2
<table>
  <tbody>
    <tr><td>{{data[0].foo}}</td></tr>
    <tr ng-repeat="d in data[1]"><td>{{d.bar}}</td></tr>
    <tr ng-repeat="d in data[2]"><td>{{d.lol}}</td></tr>
  </tbody>
</table>

Je pense que c'est valable :)

Renan Tomal Fernandes
la source
Bien que cela fonctionne, cela ne fonctionnera que si le tableau contient trois éléments.
btford le
Assurez-vous simplement que le tableau contient 3 éléments, même s'il s'agit de tableaux vides (ng-repeat avec un tableau vide ne rend tout simplement rien).
Renan Tomal Fernandes
7
Mon point était que l'OP veut probablement une solution qui fonctionne pour un nombre variable d'éléments dans le tableau. Je suppose que le codage en dur "il doit y avoir trois éléments dans ce tableau" dans le modèle serait une mauvaise solution.
btford
Dans le dernier commentaire sur sa question, il dit que les éléments n'auront pas la même structure, donc le codage en dur de chaque structure est inévitable.
Renan Tomal Fernandes