Cómo usar ng-repeat sin un elemento html

142

Necesito usar ng-repeat(en AngularJS) para enumerar todos los elementos en una matriz.

La complicación es que cada elemento de la matriz se transformará en una, dos o tres filas de una tabla.

No puedo crear html válido, si ng-repeatse usa en un elemento, ya que no se permite ningún tipo de elemento repetitivo entre <tbody>y <tr>.

Por ejemplo, si usara ng-repeat on <span>, obtendría:

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

Que no es válido html.

Pero lo que necesito ser generado es:

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

donde la primera fila ha sido generada por el primer elemento de la matriz, las siguientes tres por el segundo y la quinta y sexta por el último elemento de la matriz.

¿Cómo puedo usar ng-repeat de tal manera que el elemento html al que está vinculado 'desaparezca' durante el renderizado?

¿O hay otra solución para esto?


Aclaración: La estructura generada debería verse como a continuación. Cada elemento de la matriz puede generar entre 1-3 filas de la tabla. La respuesta idealmente debería admitir 0-n filas por elemento de matriz.

<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>          
fadedbee
fuente
Tal vez deberías usar "replace: true"? Vea esto: stackoverflow.com/questions/11426114/…
Además, ¿por qué no puedes usar ng-repeat en el propio tr?
2
@Tommy, porque "cada elemento de la matriz se transformará en una, dos o tres filas de una tabla". Si usara ng-repeat en el tr, obtendría una fila por elemento de matriz, por lo que entiendo.
fadedbee
1
OK veo. ¿No puedes simplemente aplanar el modelo antes de usarlo en el repetidor?
@ Tommy, no. Los 1-3 trs que son generados por un elemento de matriz no tienen la misma estructura.
fadedbee

Respuestas:

66

Actualización: si está utilizando Angular 1.2+, use ng-repeat-start . Ver la respuesta de @ jmagnusson.

De lo contrario, ¿qué hay de poner el ng-repeat en tbody? (AFAIK, está bien tener múltiples <tbody> s en una sola tabla).

<tbody ng-repeat="row in array">
  <tr ng-repeat="item in row">
     <td>{{item}}</td>
  </tr>
</tbody>
Mark Rajcok
fuente
62
Si bien esto podría funcionar técnicamente, es muy decepcionante que la respuesta para este caso de uso común sea que tenga que inyectar marcas arbitrarias (de lo contrario innecesarias). Tengo el mismo problema (grupos repetidos de filas: un encabezado TR con uno o más TR ​​secundarios, repetidos como un grupo). Trivial con otros motores de plantillas, parece hacky en el mejor de los casos con Angular.
Brian Moeskau
1
Convenido. Estoy tratando de hacer una repetición en los elementos DT + DD. no hay forma de hacerlo sin agregar un elemento de envoltura no válido
David Lin
2
@DavidLin, en Angular v1.2 (siempre que salga), podrá repetir sobre múltiples elementos, por ejemplo, dt y dd: youtube.com/watch?v=W13qDdJDHp8&t=17m28s
Mark Rajcok
Esto se maneja con elegancia en KnockoutJS utilizando la sintaxis de flujo de control sin contenedor: knockoutjs.com/documentation/foreach-binding.html . Espero ver a Angular hacer algo similar pronto.
Cory House
3
Esta respuesta está desactualizada, use ng-repeat-start en su lugar
iwein
73

A partir de AngularJS 1.2 hay una directiva llamada ng-repeat-startque hace exactamente lo que pides. Vea mi respuesta en esta pregunta para obtener una descripción de cómo usarlo.

jmagnusson
fuente
Angular se ha puesto al día, esta es la solución adecuada ahora.
iwein
33

Si usa ng> 1.2, aquí hay un ejemplo de uso ng-repeat-start/endsin generar etiquetas innecesarias:

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

El punto importante: para las etiquetas utilizadas ng-repeat-starty ng-repeat-endconfiguradas ng-if="0", para que no se inserten en la página. De esta manera, el contenido interno se manejará exactamente como está en knockoutjs (usando comandos en <!--...-->), y no habrá basura.

Duka Árpád
fuente
después de configurar ng-if = "0", arroja un error que dice que no se puede encontrar ng-repeat-end coincidente.
vikky MCTS
19

Es posible que desee aplanar los datos dentro de su controlador:

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

Y luego en el HTML:

<table>
  <tbody>
    <tr ng-repeat="item in flattened()"><td>{{item}}</td></tr>
  </tbody>
</table>
btford
fuente
1
No, no es. No se recomienda llamar a una función dentro de una expresión ngRepeat
Nik
En mi caso, yo tenía necesidad de añadir un separador para cada elemento en el índice = 0 y! ng-repeat-start, ng-repeat-endRomper mi diseño, por lo que la solución era crear una variable adicional añadiendo este objeto separador antes de cada elemento e iterar la nueva 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

Lo anterior es correcto pero para una respuesta más general no es suficiente. Necesitaba anidar ng-repeat, pero permanecer en el mismo nivel html, lo que significa escribir los elementos en el mismo padre. La matriz de etiquetas contiene etiquetas que también tienen una matriz de etiquetas. En realidad es un árbol.

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

Así que aquí está lo que eventualmente hice.

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

Tenga en cuenta el ng-if = "false" que oculta los divs de inicio y fin.

Debe imprimir

nombre1, nombre1_1, nombre1_2, nombre2, nombre2_1, nombre2_2,

Αλέκος
fuente
1

Solo me gustaría comentar, pero mi reputación todavía es deficiente. Así que estoy agregando otra solución que resuelve el problema también. Realmente me gustaría refutar la declaración hecha por @bmoeskau de que resolver este problema requiere una solución 'hacky en el mejor de los casos', y dado que esto surgió recientemente en una discusión a pesar de que esta publicación tiene 2 años, me gustaría agregar mi posee dos centavos:

Como ha señalado @btford, parece que estás tratando de convertir una estructura recursiva en una lista, por lo que primero debes aplanar esa estructura en una lista. Su solución hace eso, pero existe la opinión de que llamar a la función dentro de la plantilla no es elegante. si eso es cierto (honestamente, no lo sé), ¿no requeriría ejecutar la función en el controlador en lugar de la directiva?

de cualquier manera, su html requiere una lista, por lo que el alcance que lo representa debe tener esa lista para trabajar. simplemente tiene que aplanar la estructura dentro de su controlador. una vez que tenga una matriz $ scope.rows, puede generar la tabla con una sola y simple ng-repeat. Sin piratería, sin inelegancia, simplemente la forma en que fue diseñado para funcionar.

Las directivas angulares no carecen de funcionalidad. Simplemente te obligan a escribir html válido. Un colega mío tuvo un problema similar, citando a @bmoeskau en apoyo de las críticas sobre las características de plantillas / renderizado de angulares. Al mirar el problema exacto, resultó que simplemente quería generar una etiqueta abierta, luego una etiqueta cerrada en otro lugar, etc., como en los viejos tiempos, cuando concatábamos nuestro html a partir de cadenas ... ¿verdad? No.

En cuanto a aplanar la estructura en una lista, aquí hay otra solución:

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

esto funcionará para cualquier estructura en forma de árbol que tenga sub ítems. Si desea todo el elemento en lugar de una propiedad, puede acortar aún más la función de aplanamiento.

Espero que ayude.

Ar Es
fuente
Buena esa. Si bien no me ayudó directamente, abrió mi mente a otra solución. ¡Muchas gracias!
Marian Zburlea
0

para una solución que realmente funciona

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>

agregar una directiva 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]);
}());

para una breve explicación,

ng-repeat se une al <remove>elemento y se repite como debería, y debido a que hemos usado ng-repeat-start / ng-repeat-end se repite un bloque de html no solo un elemento.

entonces la directiva de eliminación personalizada coloca los <remove>elementos de inicio y finalización con<!--removed element-->

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

Creo que esto es válido :)

Renan Tomal Fernandes
fuente
Si bien esto funciona, solo funcionará si la matriz tiene tres elementos.
btford el
Solo asegúrese de que la matriz tenga 3 elementos, incluso si son matrices vacías (ng-repeat con una matriz vacía simplemente no representa nada).
Renan Tomal Fernandes
77
Mi punto fue que el OP probablemente quiera una solución que funcione para un número variable de elementos en la matriz. Supongo que codificar "debe haber tres elementos en esta matriz" en la plantilla sería una mala solución.
btford
En el último comentario sobre su pregunta, dice que los elementos no tendrán la misma estructura, por lo que es inevitable codificar cada estructura.
Renan Tomal Fernandes