Como usar ng-repeat sem um elemento html

142

Eu preciso usar ng-repeat(no AngularJS) para listar todos os elementos em uma matriz.

A complicação é que cada elemento da matriz se transforma em uma, duas ou três linhas de uma tabela.

Não consigo criar html válido, se ng-repeatfor usado em um elemento, pois nenhum tipo de elemento repetido é permitido entre <tbody>e<tr> .

Por exemplo, se eu usasse ng-repeat <span>, eu obteria:

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

Qual é o html inválido.

Mas o que eu preciso ser gerado é:

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

onde a primeira linha foi gerada pelo primeiro elemento da matriz, as próximas três pela segunda e a quinta e a sexta pelo último elemento da matriz.

Como posso usar ng-repeat de forma que o elemento html ao qual está vinculado 'desapareça' durante a renderização?

Ou existe outra solução para isso?


Esclarecimento: A estrutura gerada deve se parecer com abaixo. Cada elemento da matriz pode gerar entre 1-3 linhas da tabela. A resposta deve idealmente suportar 0-n linhas por elemento da 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
fonte
Talvez você deva usar "replace: true"? Veja isso: stackoverflow.com/questions/11426114/…
Além disso, por que você não pode usar ng-repeat no próprio tr?
2
@ Tommy, porque "cada elemento da matriz será transformado em uma, duas ou três linhas de uma tabela". Se eu usasse ng-repeat no tr, obteria uma linha por elemento da matriz, pelo que entendi.
Fadedbee
1
OK eu vejo. Você não pode simplesmente achatar o modelo antes de usá-lo no repetidor?
@ Tommy, não. Os 1-3 trs que são gerados por um elemento da matriz não têm a mesma estrutura.
Fadedbee

Respostas:

66

Atualização: se você estiver usando o Angular 1.2+, use ng-repeat-start . Veja a resposta de @ jmagnusson.

Caso contrário, que tal colocar o ng-repeat em tbody? (AFAIK, não há problema em ter vários <corpos> em uma única tabela.)

<tbody ng-repeat="row in array">
  <tr ng-repeat="item in row">
     <td>{{item}}</td>
  </tr>
</tbody>
Mark Rajcok
fonte
62
Embora isso possa funcionar tecnicamente, é muito decepcionante que a resposta para este caso de uso comum seja que você deve injetar uma marcação arbitrária (caso contrário, desnecessária). Eu tenho o mesmo problema (grupos repetidos de linhas - um cabeçalho TR com um ou mais TRs filhos, repetidos como um grupo). Trivial com outros mecanismos de template, parece hacky na melhor das hipóteses com Angular.
Brian Moeskau
1
Acordado. Estou tentando fazer uma repetição nos elementos DT + DD. não há nenhuma maneira de fazer isso sem adicionar um elemento de envolvimento inválido
David Lin
2
@DavidLin, em v1.2 Angular (sempre que ele sai) você vai ser capaz de repetir ao longo de vários elementos, por exemplo, dt e dd: youtube.com/watch?v=W13qDdJDHp8&t=17m28s
Mark Rajcok
Isso é tratado de maneira elegante no KnockoutJS usando a sintaxe de fluxo de controle sem contêiner: knockoutjs.com/documentation/foreach-binding.html . Espero que o Angular faça algo semelhante em breve.
Cory House
3
Esta resposta está desatualizado, o uso ng-repeat-start vez
Iwein
73

A partir do AngularJS 1.2, existe uma diretiva chamada ng-repeat-startque faz exatamente o que você solicita. Veja minha resposta nesta pergunta para uma descrição de como usá-lo.

jmagnusson
fonte
Angular alcançou, esta é a solução adequada agora.
Iwein
33

Se você usar ng> 1.2, aqui está um exemplo de uso ng-repeat-start/endsem gerar tags desnecessárias:

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

O ponto importante: para tags usadas ng-repeat-starte ng-repeat-enddefinidas ng-if="0", para não serem inseridas na página. Dessa maneira, o conteúdo interno será tratado exatamente como em knockoutjs (usando comandos in <!--...-->) e não haverá lixo.

Duka Árpád
fonte
depois de definir ng-if = "0", gera um erro ao dizer incapaz de encontrar ng-repeat-end correspondente.
Vikky MCTS
19

Você pode achatar os dados em seu 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;
  }
}

E então no HTML:

<table>
  <tbody>
    <tr ng-repeat="item in flattened()"><td>{{item}}</td></tr>
  </tbody>
</table>
btford
fonte
1
Não, não é. Chamar uma função dentro de uma expressão ngRepeat não é absolutamente recomendado
Nik
No meu caso, eu precisei adicionar um separador para cada elemento em que o índice! = 0 e ng-repeat-start, ng-repeat-endinterrompesse meu design, a solução foi criar uma variável extra adicionando esse objeto separador antes de cada elemento e iterar a nova 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

O exposto acima está correto, mas para uma resposta mais geral, não é suficiente. Eu precisava aninhar ng-repeat, mas permanecer no mesmo nível de html, ou seja, escrever os elementos no mesmo pai. A matriz de tags contém tags que também possuem uma matriz de tags. Na verdade, é uma árvore.

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

Então aqui está o que eu finalmente fiz.

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

Observe o ng-if = "false" que oculta as divs inicial e final.

Deve imprimir

nome1, nome1_1, nome1_2, nome2, nome2_1, nome2_2,

Αλέκος
fonte
1

Gostaria apenas de comentar, mas ainda falta minha reputação. Então, eu estou adicionando outra solução que resolve o problema também. Eu realmente gostaria de refutar a afirmação feita por @bmoeskau de que resolver este problema requer uma solução "hacky na melhor das hipóteses" e, como isso surgiu recentemente em uma discussão, mesmo que este post tenha 2 anos, eu gostaria de adicionar meu possui dois centavos:

Como o @btford apontou, você parece estar tentando transformar uma estrutura recursiva em uma lista; portanto, você deve achatar essa estrutura em uma lista primeiro. Sua solução faz isso, mas há uma opinião de que chamar a função dentro do modelo é deselegante. se isso for verdade (honestamente, eu não sei), isso não exigiria apenas a execução da função no controlador, em vez da diretiva?

de qualquer forma, seu html requer uma lista; portanto, o escopo que o renderiza deve ter essa lista para trabalhar. você simplesmente precisa achatar a estrutura dentro do seu controlador. Depois de ter uma matriz $ scope.rows, você pode gerar a tabela com uma única e simples repetição de ng. Sem hackers, sem deselegância, simplesmente da maneira como foi projetado para funcionar.

As diretivas angulares não estão faltando funcionalidade. Eles simplesmente forçam você a escrever html válido. Um colega meu teve um problema semelhante, citando @bmoeskau em apoio a críticas sobre recursos de modelagem / renderização angulares. Ao analisar o problema exato, descobriu-se que ele simplesmente queria gerar uma marca aberta, depois uma marca fechada em outro lugar, etc., como nos bons velhos tempos em que concatávamos nosso html de strings .. certo? não.

Quanto ao nivelamento da estrutura em uma lista, aqui está outra solução:

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

isso funcionará para qualquer estrutura de árvore que tenha subitens. Se você quiser o item inteiro em vez de uma propriedade, poderá reduzir ainda mais a função de nivelamento.

espero que ajude.

Ar Es
fonte
Agradável. Embora não tenha me ajudado diretamente, abriu minha mente para outra solução. Muito obrigado!
Marian Zburlea
0

para uma solução 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>

adicione uma diretiva 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 uma breve explicação,

ng-repeat liga-se ao <remove>elemento e faz um loop como deveria, e porque usamos ng-repeat-start / ng-repeat-end, ele faz um loop de um bloco de html e não apenas de um elemento.

a diretiva de remoção personalizada coloca os <remove>elementos de início e fim com<!--removed element-->

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

Eu acho que isso é válido :)

Renan Tomal Fernandes
fonte
Enquanto isso funcionar, só funcionará se a matriz tiver três elementos.
btford
Apenas certifique-se de que a matriz tenha 3 elementos, mesmo que sejam matrizes vazias (ng-repeat com uma matriz vazia simplesmente não renderiza nada).
Renan Tomal Fernandes
7
Meu argumento foi que o OP provavelmente deseja uma solução que funcione para um número variável de itens na matriz. Presumo que a codificação "deve haver três itens nessa matriz" no modelo seria uma solução ruim.
btford
No último comentário sobre sua pergunta, ele diz que os elementos não terão a mesma estrutura, de modo que cada código é inevitável.
Renan Tomal Fernandes