Angular ng-repeat adicionar linha de bootstrap a cada 3 ou 4 cols

111

Estou procurando o padrão certo para injetar uma classe de linha de bootstrap a cada 3 colunas. Eu preciso disso porque cols não tem altura fixa (e eu não quero consertar), então quebra meu design!

Aqui está o meu código:

<div ng-repeat="product in products">
    <div ng-if="$index % 3 == 0" class="row">
        <div class="col-sm-4" >
            ...
        </div>
    </div>
</div>

Mas ele exibe apenas um produto em cada linha. O que eu quero como resultado final é:

<div class="row">
    <div class="col-sm4"> ... </div>
    <div class="col-sm4"> ... </div>
    <div class="col-sm4"> ... </div>
</div>
<div class="row">
    <div class="col-sm4"> ... </div>
    <div class="col-sm4"> ... </div>
    <div class="col-sm4"> ... </div>
</div>

Posso conseguir isso apenas com o padrão ng-repeat (sem diretiva ou controlador)? Os documentos apresentam ng-repeat-start e ng-repeat-end, mas não consigo descobrir como usá-los neste caso de uso! Eu sinto que isso é algo que frequentemente usamos em modelos de bootstrap! ? obrigado

abraços
fonte
Eu acho que você deve modelar seus dados de uma forma que se adapte ao seu projeto, provavelmente deve ser um array ou objeto multidimensional, com representação de linhas e colunas, então você deve iterar sobre as linhas e usar a diretiva "ng-class" da classe condicional e dentro da linha você deve então iterar nas colunas.
antanas_sepikas
Interessante e certamente uma solução funcional, mas no dia em que eu quiser exibir 4 produtos em uma linha em vez de 3, eu tenho, para modificar minha estrutura de dados, eu preferiria que isso ficasse no "escopo" da funcionalidade de exibição pura ...
hugsbrugs
Entendo, então você provavelmente deve iterar em pedaços como na resposta de Ariel, também você pode achar esta postagem stackoverflow.com/questions/18564888/… útil.
antanas_sepikas
Acho que é exatamente isso que você está procurando: stackoverflow.com/a/30426750/1943442
user1943442
relacionado stackoverflow.com/a/25838091/759452
Adrien Be

Respostas:

164

A resposta mais votada, embora eficaz, não é o que eu consideraria ser a forma angular, nem está usando as próprias classes de bootstrap destinadas a lidar com essa situação. Como @claies mencionou, a .clearfixclasse é destinada a situações como essas. Na minha opinião, a implementação mais limpa é a seguinte:

<div class="row">
    <div ng-repeat="product in products">
        <div class="clearfix" ng-if="$index % 3 == 0"></div>
        <div class="col-sm-4">
            <h2>{{product.title}}</h2>
        </div>
    </div>
</div>

Essa estrutura evita a indexação confusa da matriz de produtos, permite a notação de ponto limpa e faz uso da classe clearfix para sua finalidade.

Duncan
fonte
6
Esta é uma boa ideia, no entanto, se você estiver interessado em usar o flexbox, você deve usá-lo na linha e não nos divs dentro das linhas para permitir que cada caixa / div tenha a mesma altura. Clearfix é ótimo, mas não ajuda a manter tudo alinhado.
Embalagem Codificada de
3
Esta é minha resposta preferida. Muito limpo e fácil.
Hinrich,
1
Funciona muito bem para minha implementação também! :)
Will Strohl,
1
Ótimo ... Isso deve ser votado como uma resposta aceita!
Velu
3
Esta é a resposta perfeita
Deepu
148

Eu sei que é um pouco tarde, mas ainda pode ajudar alguém. Eu fiz assim:

<div ng-repeat="product in products" ng-if="$index % 3 == 0" class="row">
    <div class="col-xs-4">{{products[$index]}}</div>
    <div class="col-xs-4" ng-if="products.length > ($index + 1)">{{products[$index + 1]}}</div>
    <div class="col-xs-4" ng-if="products.length > ($index + 2)">{{products[$index + 2]}}</div>
</div>

jsfiddle

alpar
fonte
3
realmente me ajudou muito !! Obrigado.
SupimpaAllTheWay
Isso é muito fácil de implementar! Obrigado!
Manas Bajaj de
Me ajudou muito. Obrigado!
Ahmad Ajmi
17
Ótima solução, no entanto, não verifica se $ index + 1 e $ index +2 ultrapassaram os limites do array. Os dois últimos divs exigem ng-if="$index+1 < products.length"eng-if="$index+2 < products.length"
Mustafa Ozturk
Podemos fazer isso para o par chave e valor. obter a próxima chave? Em vez de obter $ index + 1
bpbhat77 de
25

Ok, esta solução é muito mais simples do que as que já estão aqui, e permite diferentes larguras de coluna para diferentes larguras de dispositivo.

<div class="row">
    <div ng-repeat="image in images">
        <div class="col-xs-6 col-sm-4 col-md-3 col-lg-2">
            ... your content here ...
        </div>
        <div class="clearfix visible-lg" ng-if="($index + 1) % 6 == 0"></div>
        <div class="clearfix visible-md" ng-if="($index + 1) % 4 == 0"></div>
        <div class="clearfix visible-sm" ng-if="($index + 1) % 3 == 0"></div>
        <div class="clearfix visible-xs" ng-if="($index + 1) % 2 == 0"></div>
    </div>
</div>

Observe que a % 6parte deve ser igual ao número de colunas resultantes. Portanto, se no elemento de coluna você tiver a classe col-lg-2, haverá 6 colunas, então use... % 6 .

Esta técnica (excluindo o ng-if) está realmente documentada aqui: Bootstrap docs


fonte
1
Na minha opinião, esta é a melhor solução.
user216661
Se for novo no bootstrap, é fácil ignorar o fato de que definir as linhas não é necessário. Isso funcionou perfeitamente e é uma versão um pouco mais completa da solução de Duncan.
fosplait
Isso é exatamente o que eu estava procurando.
Hinrich
@phosplait Qual é a vantagem disso sobre Duncan?
Jeppe
17

Embora o que você deseja realizar possa ser útil, há outra opção que eu acredito que você pode estar negligenciando que é muito mais simples.

Você está correto, as tabelas do Bootstrap agem estranhamente quando você tem colunas que não têm altura fixa. No entanto, existe uma classe de bootstrap criada para combater esse problema e realizar redefinições responsivas .

simplesmente crie um vazio <div class="clearfix"></div>antes do início de cada nova linha para permitir que os flutuadores sejam redefinidos e as colunas retornem às suas posições corretas.

aqui está um bootply .

Claies
fonte
Isso não resolve os 15px negativos de margem que cada .row tem para bootstrap.
Logus
Isso funciona flexpara fazer colunas com a mesma altura?
Alisson
16

Obrigado por suas sugestões, você me pegou no caminho certo!

Vamos para uma explicação completa:

  • Por padrão, AngularJS http get query retorna um objeto

  • Portanto, se você quiser usar a função @Ariel Array.prototype.chunk, você deve primeiro transformar o objeto em um array.

  • E então, para usar a função chunk em SEU CONTROLADOR, caso contrário, se usada diretamente no ng-repeat, ele levará você a um erro infdig . O controlador final parece:

    // Initialize products to empty list
    $scope.products = [];
    
    // Load products from config file
    $resource("/json/shoppinglist.json").get(function (data_object)
    {
        // Transform object into array
        var data_array =[];
        for( var i in data_object ) {
            if (typeof data_object[i] === 'object' && data_object[i].hasOwnProperty("name")){
                data_array.push(data_object[i]);
            }
        }
        // Chunk Array and apply scope
        $scope.products = data_array.chunk(3);
    });

E o HTML se torna:

<div class="row" ng-repeat="productrow in products">

    <div class="col-sm-4" ng-repeat="product in productrow">

Por outro lado, decidi retornar diretamente um array [] em vez de um objeto {} do meu arquivo JSON. Desta forma, o controlador se torna (observe a sintaxe específica isArray: true ):

    // Initialize products to empty list 
    $scope.products = [];

    // Load products from config file
    $resource("/json/shoppinglist.json").query({method:'GET', isArray:true}, function (data_array)
    {
        $scope.products = data_array.chunk(3);
    });

HTML permanece igual ao anterior.

OTIMIZAÇÃO

A última questão em suspense é: como torná-lo 100% AngularJS sem estender o array javascript com a função chunk ... se algumas pessoas estiverem interessadas em nos mostrar se ng-repeat-start e ng-repeat-end são o caminho a seguir. . Estou curioso ;)

SOLUÇÃO DE ANDREW

Graças a @Andrew, agora sabemos que adicionar uma classe bootstrap clearfix a cada três (ou qualquer número) elemento corrige o problema de exibição da altura do bloco diferente.

Então, o HTML se torna:

<div class="row">

    <div ng-repeat="product in products">

        <div ng-if="$index % 3 == 0" class="clearfix"></div>

        <div class="col-sm-4"> My product descrition with {{product.property}}

E seu controlador permanece bastante suave com a função chunck removida :

// Initialize products to empty list 
        $scope.products = [];

        // Load products from config file
        $resource("/json/shoppinglist.json").query({method:'GET', isArray:true}, function (data_array)
        {
            //$scope.products = data_array.chunk(3);
            $scope.products = data_array;
        });
abraços
fonte
7

Você pode fazer isso sem uma diretiva, mas não tenho certeza se é a melhor maneira. Para fazer isso, você deve criar um array de array a partir dos dados que deseja exibir na tabela e, depois disso, usar 2 ng-repeat para iterar pelo array.

para criar a matriz para exibição, use esta função como em products.chunk (3)

Array.prototype.chunk = function(chunkSize) {
    var array=this;
    return [].concat.apply([],
        array.map(function(elem,i) {
            return i%chunkSize ? [] : [array.slice(i,i+chunkSize)];
        })
    );
}

e então fazer algo assim usando 2 ng-repeat

<div class="row" ng-repeat="row in products.chunk(3)">
  <div class="col-sm4" ng-repeat="item in row">
    {{item}}
  </div>
</div>
Ariel Henryson
fonte
7

Baseado na solução da Alpar, usando apenas modelos com repetição de ng anidada. Funciona com linhas cheias e parcialmente vazias:

<div data-ng-app="" data-ng-init="products='soda','beer','water','milk','wine']" class="container">
    <div ng-repeat="product in products" ng-if="$index % 3 == 0" class="row">
        <div class="col-xs-4" 
            ng-repeat="product in products.slice($index, ($index+3 > products.length ? 
            products.length : $index+3))"> {{product}}</div>
    </div>
</div>

JSFiddle

Juangui Jordán
fonte
5

Acabei de fazer uma solução funcionando apenas em template. A solução é

    <span ng-repeat="gettingParentIndex in products">
        <div class="row" ng-if="$index<products.length/2+1">    <!-- 2 columns -->
            <span ng-repeat="product in products">
                <div class="col-sm-6" ng-if="$index>=2*$parent.$index && $index <= 2*($parent.$index+1)-1"> <!-- 2 columns -->
                    {{product.foo}}
                </div>
            </span>
        </div>
    </span>

O ponto está usando dados duas vezes, um é para um loop externo. As tags de span extras permanecerão, mas isso depende de como você negocia.

Se for um layout de 3 colunas, será como

    <span ng-repeat="gettingParentIndex in products">
        <div class="row" ng-if="$index<products.length/3+1">    <!-- 3 columns -->
            <span ng-repeat="product in products">
                <div class="col-sm-4" ng-if="$index>=3*$parent.$index && $index <= 3*($parent.$index+1)-1"> <!-- 3 columns -->
                    {{product.foo}}
                </div>
            </span>
        </div>
    </span>

Honestamente eu queria

$index<Math.ceil(products.length/3)

Embora não tenha funcionado.

wataru
fonte
Tentei esta solução para implementar 2 elementos em cada linha. Por exemplo, eu tenho 5 elementos em uma lista, então a saída que devo ter é 3 linhas com 2 elementos / colunas nas primeiras 2 linhas e 1 coluna na última linha. O problema é que estou obtendo 5 linhas aqui com as últimas 2 linhas vazias. Quer saber como consertar isso? Obrigado
Maverick Riz
@MaverickAzy, obrigado por tentar. Eu sei que há um problema que se a altura desses elementos for diferente, não funciona bem.
wataru
A altura dos elementos é realmente a mesma. O problema é que devo obter apenas 3 linhas, mas obter 5 linhas com as últimas 2 linhas vazias. Você pode me dizer se products.length é 5, então 5/2 + 1 =? Essa lógica não é clara para mim na linha nº 2 para a linha de classe.
Maverick Riz
@MaverickAzy, essas linhas vazias devem ser geradas como estão. Isso bagunça seu layout?
wataru
não, não bagunça o layout. A única preocupação é com relação às linhas vazias. Agradeço muito se você puder me ajudar com isso. Obrigado
Maverick Riz
5

Apenas mais uma pequena melhoria sobre a resposta @Duncan e as outras respostas com base no elemento clearfix. Se você quiser tornar o conteúdo clicável, você precisará de um z-index> 0 nele ou o clearfix irá sobrepor o conteúdo e controlar o clique.

Este é o exemplo que não está funcionando (você não pode ver o ponteiro do cursor e clicar não fará nada):

<div class="row">
    <div ng-repeat="product in products">
        <div class="clearfix" ng-if="$index % 3 == 0"></div>
        <div class="col-sm-4" style="cursor: pointer" ng-click="doSomething()">
            <h2>{{product.title}}</h2>
        </div>
    </div>
</div>

Embora este seja o único :

<div class="row">
    <div ng-repeat-start="product in products" class="clearfix" ng-if="$index % 3 == 0"></div>
    <div ng-repeat-end class="col-sm-4" style="cursor: pointer; z-index: 1" ng-click="doSomething()">
            <h2>{{product.title}}</h2>
    </div>
</div>

Eu adicionei z-index: 1para que o conteúdo aumentasse sobre o clearfix e removi o contêiner div usando em vez disso ng-repeat-starte ng-repeat-end(disponível em AngularJS 1.2) porque ele fez o z-index não funcionar.

Espero que isto ajude!

Atualizar

Plunker: http://plnkr.co/edit/4w5wZj

McGiogen
fonte
Isso funciona com flexem linhas para fazer colunas com a mesma altura?
Alisson
Não tenho certeza se entendi sua pergunta. Este é um botão rápido para mostrar o que esse código faz: plnkr.co/edit/4w5wZj?p=preview . Em palavras, o clearfix alinha corretamente a segunda linha de títulos: todos começam do mesmo ponto, mas ainda não têm a mesma altura (como você pode ver graças à cor de fundo). Tente excluir a classe clearfix para ver qual é o comportamento padrão. Usei o flexbox apenas uma ou duas vezes, mas ele tem muitas propriedades css e tenho certeza de que você pode encontrar o que está procurando.
McGiogen
bootstrap fornece um exemplo de como fazer todas as colunas na mesma linha para obter a mesma altura da coluna mais alta. Eu tive que usar isso. O problema é que ele perde a capacidade de quebrar em uma nova linha quando há mais de 12 colunas, então você precisa criar manualmente novas linhas. Depois de pesquisar um pouco mais, consegui uma solução e postei aqui como resposta, embora não saiba se é a melhor. Obrigado de qualquer maneira, sua resposta me ajudou!
Alisson de
É a primeira vez que vejo esse exemplo, é muito útil! Feliz em te ajudar.
McGiogen
4

eu resolvi isso usando ng-class

<div ng-repeat="item in items">
    <div ng-class="{ 'row': ($index + 1) % 4 == 0 }">
        <div class="col-md-3">
            {{item.name}}
        </div>
    </div>
</div>
Josip
fonte
2

A melhor maneira de aplicar uma classe é usar ng-class. Ele pode ser usado para aplicar classes com base em alguma condição.

<div ng-repeat="product in products">
   <div ng-class="getRowClass($index)">
       <div class="col-sm-4" >
           <!-- your code -->
       </div>
   </div>

e então em seu controlador

$scope.getRowClass = function(index){
    if(index%3 == 0){
     return "row";
    }
}
Rajiv
fonte
2

Depois de combinar muitas respostas e sugestões aqui, esta é a minha resposta final, que funciona bem com flex, o que nos permite fazer colunas com altura igual, também verifica o último índice, e você não precisa repetir o HTML interno. Não usa clearfix:

<div ng-repeat="prod in productsFiltered=(products | filter:myInputFilter)" ng-if="$index % 3 == 0" class="row row-eq-height">
    <div ng-repeat="i in [0, 1, 2]" ng-init="product = productsFiltered[$parent.$parent.$index + i]"  ng-if="$parent.$index + i < productsFiltered.length" class="col-xs-4">
        <div class="col-xs-12">{{ product.name }}</div>
    </div>
</div>

A saída será algo assim:

<div class="row row-eq-height">
    <div class="col-xs-4">
        <div class="col-xs-12">
            Product Name
        </div>
    </div>
    <div class="col-xs-4">
        <div class="col-xs-12">
            Product Name
        </div>
    </div>
    <div class="col-xs-4">
        <div class="col-xs-12">
            Product Name
        </div>
    </div>
</div>
<div class="row row-eq-height">
    <div class="col-xs-4">
        <div class="col-xs-12">
            Product Name
        </div>
    </div>
    <div class="col-xs-4">
        <div class="col-xs-12">
            Product Name
        </div>
    </div>
    <div class="col-xs-4">
        <div class="col-xs-12">
            Product Name
        </div>
    </div>
</div>
Alisson
fonte
1

Uma pequena modificação na solução de @alpar

<div data-ng-app="" data-ng-init="products=['A','B','C','D','E','F', 'G','H','I','J','K','L']" class="container">
    <div ng-repeat="product in products" ng-if="$index % 6 == 0" class="row">
        <div class="col-xs-2" ng-repeat="idx in [0,1,2,3,4,5]">
        {{products[idx+$parent.$index]}} <!-- When this HTML is Big it's useful approach -->
        </div>
    </div>
</div>

jsfiddle

Jaym
fonte
0

Isso funcionou para mim, sem emenda ou qualquer coisa necessária:

HTML

<div class="row" ng-repeat="row in rows() track by $index">
    <div class="col-md-3" ng-repeat="item in items" ng-if="indexInRange($index,$parent.$index)"></div>
</div>

JavaScript

var columnsPerRow = 4;
$scope.rows = function() {
  return new Array(columnsPerRow);
};
$scope.indexInRange = function(columnIndex,rowIndex) {
  return columnIndex >= (rowIndex * columnsPerRow) && columnIndex < (rowIndex * columnsPerRow) + columnsPerRow;
};
robbymarston
fonte
0

Born Solutions é o melhor, só preciso ajustar um pouco para atender às necessidades, eu tive diferentes soluções responsivas e mudei um pouco

<div ng-repeat="post in posts">
    <div class="vechicle-single col-lg-4 col-md-6 col-sm-12 col-xs-12">
    </div>
    <div class="clearfix visible-lg" ng-if="($index + 1) % 3 == 0"></div>
    <div class="clearfix visible-md" ng-if="($index + 1) % 2 == 0"></div>
    <div class="clearfix visible-sm" ng-if="($index + 1) % 1 == 0"></div>
    <div class="clearfix visible-xs" ng-if="($index + 1) % 1 == 0"></div>
</div>
Kiko Seijo
fonte
0

Com base na resposta da Alpar, aqui está uma maneira mais generalizada de dividir uma única lista de itens em vários contêineres (linhas, colunas, baldes, qualquer que seja):

<div class="row" ng-repeat="row in [0,1,2]">
  <div class="col" ng-repeat="item in $ctrl.items" ng-if="$index % 3 == row">
    <span>{{item.name}}</span>
  </div>
</div> 

para uma lista de 10 itens, gera:

<div class="row">
  <div class="col"><span>Item 1</span></div>
  <div class="col"><span>Item 4</span></div>
  <div class="col"><span>Item 7</span></div>
  <div class="col"><span>Item 10</span></div>
</div> 
<div class="row">
  <div class="col"><span>Item 2</span></div>
  <div class="col"><span>Item 5</span></div>
  <div class="col"><span>Item 8</span></div>
</div> 
<div class="row">
  <div class="col"><span>Item 3</span></div>
  <div class="col"><span>Item 6</span></div>
  <div class="col"><span>Item 9</span></div>
</div> 

O número de recipientes pode ser rapidamente codificado em uma função de controlador:

JS (ES6)

$scope.rowList = function(rows) {
  return Array(rows).fill().map((x,i)=>i);
}
$scope.rows = 2;

HTML

<div class="row" ng-repeat="row in rowList(rows)">
  <div ng-repeat="item in $ctrl.items" ng-if="$index % rows == row">
    ...

Essa abordagem evita duplicar a marcação do item ( <span>{{item.name}}</span>neste caso) no modelo de origem - não uma grande vitória para um período simples, mas para uma estrutura DOM mais complexa (que eu tinha), isso ajuda a manter o modelo SECO.

Mark Chitty
fonte
0

Atualização 2019 - Bootstrap 4

Uma vez que o Bootstrap 3 usava flutuadores, é necessário que o clearfix resete a cada n (3 ou 4) colunas ( .col-*) no.row para evitar empacotamento desigual de colunas.

Agora que o Bootstrap 4 usa o flexbox , não há mais necessidade de envolver as colunas em .rowtags separadas ou inserir divs extras para forçar cols a envolver todos os n colunas.

Você pode simplesmente repetir todas as colunas em um único .rowcontêiner.

Por exemplo, 3 colunas em cada linha visual são:

<div class="row">
     <div class="col-4">...</div>
     <div class="col-4">...</div>
     <div class="col-4">...</div>
     <div class="col-4">...</div>
     <div class="col-4">...</div>
     <div class="col-4">...</div>
     <div class="col-4">...</div>
     (...repeat for number of items)
</div>

Portanto, para o Bootstrap, a repetição do ng é simplesmente:

  <div class="row">
      <div class="col-4" ng-repeat="item in items">
          ... {{ item }}
      </div>
  </div>

Demo: https://www.codeply.com/go/Z3IjLRsJXX

Zim
fonte
0

Fiz apenas usando boostrap, você deve ter muito cuidado na localização da linha e da coluna, aqui está o meu exemplo.

<section>
<div class="container">
        <div ng-app="myApp">
        
                <div ng-controller="SubregionController">
                    <div class="row text-center">
                        <div class="col-md-4" ng-repeat="post in posts">
                            <div >
                                <div>{{post.title}}</div>
                            </div>
                        </div>
                    
                    </div>
                </div>        
        </div>
    </div>
</div> 

</section>

Alexis Suárez
fonte