Come usare ng-repeat senza un elemento html


142

Devo usare ng-repeat(in AngularJS) per elencare tutti gli elementi in un array.

La complicazione è che ogni elemento dell'array si trasformerà in una, due o tre righe di una tabella.

Non riesco a creare un codice HTML valido, se ng-repeatutilizzato su un elemento, poiché non è consentito alcun tipo di elemento ripetuto tra <tbody>e <tr>.

Ad esempio, se avessi usato ng-repeat su <span>, avrei ottenuto:

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

Che è html non valido.

Ma quello che devo generare è:

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

dove la prima riga è stata generata dal primo elemento dell'array, i tre successivi dal secondo e il quinto e il sesto dall'ultimo elemento dell'array.

Come posso usare ng-repeat in modo tale che l'elemento html a cui è legato 'scompaia' durante il rendering?

O c'è un'altra soluzione a questo?


Chiarimento: la struttura generata dovrebbe apparire come di seguito. Ogni elemento dell'array può generare tra 1-3 righe della tabella. La risposta dovrebbe idealmente supportare 0-n righe per elemento dell'array.

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

Forse dovresti usare "sostituisci: vero"? Vedere questo: stackoverflow.com/questions/11426114/...

Inoltre, perché non puoi usare ng-repeat sul tr stesso?

2
@ Tommy, perché "ogni elemento dell'array si trasformerà in una, due o tre righe di una tabella". Se avessi usato ng-repeat sul travrei ottenuto una riga per elemento dell'array, per quanto ho capito.
fadedbee,

1
Va bene, ho capito. Non puoi semplicemente appiattire il modello prima di usarlo nel ripetitore?

@ Tommy, no. Gli 1-3 trs generati da un elemento array non hanno la stessa struttura.
fadedbee,

Risposte:


66

Aggiornamento: se si utilizza Angular 1.2+, utilizzare ng-repeat-start . Vedi la risposta di @ jmagnusson.

Altrimenti, che ne dici di mettere il ng-repeat sul corpo? (AFAIK, va bene avere più <tbody> in una singola tabella.)

<tbody ng-repeat="row in array">
  <tr ng-repeat="item in row">
     <td>{{item}}</td>
  </tr>
</tbody>

62
Anche se questo potrebbe tecnicamente funzionare, è molto deludente che la risposta a questo caso d'uso comune sia che devi iniettare un markup arbitrario (altrimenti non necessario). Ho lo stesso problema (gruppi ripetuti di righe: un'intestazione TR con uno o più TR secondari, ripetuta come gruppo). Triviale con altri motori di template, hacky al massimo con Angular sembra.
Brian Moeskau,

1
Concordato. Sto provando a ripetere gli elementi DT + DD. non c'è modo di farlo senza aggiungere un elemento di avvolgimento non valido
David Lin,

2
@DavidLin, in Angular v1.2 (ogni volta che esce) sarai in grado di ripetere più elementi, ad esempio dt e dd: youtube.com/watch?v=W13qDdJDHp8&t=17m28s
Mark Rajcok,

Questo viene gestito in modo elegante in KnockoutJS usando la sintassi del flusso di controllo senza contenitore: knockoutjs.com/documentation/foreach-binding.html . Spero di vedere presto Angular fare qualcosa di simile.
Cory House,

3
Questa risposta è obsoleta, usa invece ng-repeat-start
iwein

73

A partire da AngularJS 1.2 c'è una direttiva chiamata ng-repeat-startche fa esattamente quello che chiedi. Vedi la mia risposta in questa domanda per una descrizione di come usarlo.


Angular ha raggiunto, questa è la soluzione corretta ora.
iwein,

33

Se usi ng> 1.2, ecco un esempio dell'uso ng-repeat-start/endsenza generare tag non necessari:

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

Il punto importante: per i tag utilizzati ng-repeat-starte ng-repeat-endimpostati ng-if="0", per non essere inseriti nella pagina. In questo modo il contenuto interno verrà gestito esattamente come in knockoutjs (usando i comandi in <!--...-->) e non ci sarà spazzatura.


dopo aver impostato ng-if = "0", genera un errore dicendo che non è possibile trovare ng-repeat-end corrispondente.
vikky MCTS

19

Potresti voler appiattire i dati all'interno del tuo controller:

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

E poi in HTML:

<table>
  <tbody>
    <tr ng-repeat="item in flattened()"><td>{{item}}</td></tr>
  </tbody>
</table>

1
No non lo è. Non è assolutamente consigliabile chiamare una funzione all'interno di un'espressione ngRepeat
Nik

Nel mio caso, avevo bisogno di aggiungere un separatore per ogni elemento in cui l'indice! = 0 e ng-repeat-start, ng-repeat-endinterrompere il mio progetto, quindi la soluzione era quella di creare una variabile aggiuntiva aggiungendo questo oggetto separatore prima di ogni elemento e ripetendo il nuovo 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

Quanto sopra è corretto ma per una risposta più generale non è sufficiente. Avevo bisogno di annidare ng-repeat, ma rimanere sullo stesso livello html, nel senso che scrivere gli elementi nello stesso genitore. L'array di tag contiene tag che hanno anche un array di tag. In realtà è un albero.

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

Quindi ecco cosa ho fatto alla fine.

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

Nota ng-if = "false" che nasconde i div di inizio e fine.

Dovrebbe stampare

nome1, name1_1, name1_2, nome2, name2_1, name2_2,


1

Vorrei solo commentare, ma la mia reputazione è ancora carente. Quindi sto aggiungendo un'altra soluzione che risolve anche il problema. Vorrei davvero smentire l'affermazione fatta da @bmoeskau secondo cui la risoluzione di questo problema richiede una soluzione "hacky at best", e dato che questo è emerso recentemente in una discussione anche se questo post ha 2 anni, vorrei aggiungere il mio possedere due centesimi:

Come ha sottolineato @btford, sembra che tu stia cercando di trasformare una struttura ricorsiva in un elenco, quindi dovresti prima appiattire quella struttura in un elenco. La sua soluzione lo fa, ma c'è un'opinione che chiamare la funzione all'interno del modello sia inelegante. se questo è vero (onestamente, non lo so) non richiederebbe solo l'esecuzione della funzione nel controller piuttosto che la direttiva?

in entrambi i casi, il tuo html richiede un elenco, quindi l'ambito che lo rende dovrebbe avere quell'elenco con cui lavorare. devi semplicemente appiattire la struttura all'interno del tuo controller. una volta che hai un array $ scope.rows, puoi generare la tabella con una semplice e semplice ripetizione ng. Nessun hacking, nessuna ineleganza, semplicemente il modo in cui è stato progettato per funzionare.

Le direttive angolari non mancano di funzionalità. Ti costringono semplicemente a scrivere HTML valido. Un mio collega ha avuto un problema simile, citando @bmoeskau a sostegno delle critiche sugli angolari che modellano / rendono le funzionalità. Quando si è verificato il problema esatto, si è scoperto che voleva semplicemente generare un tag aperto, quindi un tag vicino da qualche altra parte, ecc. Proprio come ai bei vecchi tempi in cui avremmo concesso il nostro codice HTML alle stringhe ... giusto? no.

per quanto riguarda l'appiattimento della struttura in un elenco, ecco un'altra soluzione:

// 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']

questo funzionerà per qualsiasi struttura ad albero che ha elementi secondari. Se si desidera l'intero elemento anziché una proprietà, è possibile ridurre ulteriormente la funzione di appiattimento.

spero che aiuti.


Ben fatto. Sebbene non mi abbia aiutato direttamente, mi ha aperto la mente a un'altra soluzione. Molte grazie!
Marian Zburlea,

0

per una soluzione che funziona davvero

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>

aggiungi una direttiva 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]);
}());

per una breve spiegazione,

ng-repeat si lega <remove>all'elemento e ai loop come dovrebbe, e poiché abbiamo usato ng-repeat-start / ng-repeat-end, esegue il loop di un blocco di HTML non solo di un elemento.

quindi la direttiva di rimozione personalizzata posiziona gli <remove>elementi start e finish con<!--removed element-->


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

Penso che questo sia valido :)


Mentre funziona, funzionerà solo se l'array ha tre elementi.
btford,

Assicurati solo che l'array abbia 3 elementi, anche se sono array vuoti (ng-repeat con un array vuoto semplicemente non esegue il rendering di nulla).
Renan Tomal Fernandes,

7
Il mio punto era che l'OP probabilmente vuole una soluzione che funzioni per un numero variabile di elementi nell'array. Presumo che la codifica "ci siano tre elementi in questo array" nel modello sarebbe una soluzione scadente.
btford,

Nell'ultimo commento alla sua domanda afferma che gli elementi non avranno la stessa struttura, quindi codificare ogni struttura è inevitabile.
Renan Tomal Fernandes,
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.