Angular ng-repeat agrega una fila de arranque cada 3 o 4 columnas

111

Estoy buscando el patrón correcto para inyectar una clase de fila de arranque cada 3 columnas. Necesito esto porque cols no tiene una altura fija (y no quiero arreglar una), ¡así que rompe mi diseño!

Aquí está mi código:

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

Pero solo muestra un producto en cada fila. Lo que quiero como resultado final es:

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

¿Puedo lograr esto con solo el patrón ng-repeat (sin directiva o controlador)? Los documentos introducen ng-repeat-start y ng-repeat-end, ¡pero no puedo entender cómo usarlo en este caso de uso! ¡Siento que esto es algo que usamos a menudo en las plantillas de arranque! ? Gracias

abrazos
fuente
Creo que debería modelar sus datos de una manera que se ajuste a su diseño, probablemente debería ser una matriz u objeto multidimensional, con representación de filas y columnas, luego debería iterar sobre las filas y usar la directiva de clase condicional "ng-class" y la fila interior luego debe iterar sobre las columnas.
antanas_sepikas
Interesante y ciertamente una solución que funciona, pero el día que quiera mostrar 4 productos por fila en lugar de 3, tengo que modificar mi estructura de datos, preferiría que esto permanezca en el "alcance" de la funcionalidad de visualización pura ...
hugsbrugs
Ya veo, entonces probablemente debería iterar en trozos como en la respuesta de Ariel, también puede encontrar esta publicación stackoverflow.com/questions/18564888/… útil.
antanas_sepikas
Creo que esto es exactamente lo que estás buscando: stackoverflow.com/a/30426750/1943442
user1943442
relacionado stackoverflow.com/a/25838091/759452
Adrien Be

Respuestas:

164

La respuesta más votada, aunque efectiva, no es lo que yo consideraría la forma angular, ni está utilizando las propias clases de bootstrap que están destinadas a lidiar con esta situación. Como mencionó @claies, la .clearfixclase está destinada a situaciones como estas. En mi opinión, la implementación más limpia es la siguiente:

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

Esta estructura evita la indexación desordenada de la matriz de productos, permite una notación de puntos limpia y hace uso de la clase clearfix para el propósito previsto.

Duncan
fuente
6
Esta es una buena idea, sin embargo, si está interesado en usar flexbox, debe usar esto en la fila y no en los divs dentro de las filas para permitir que cada caja / div tenga la misma altura. Clearfix es excelente, pero no ayuda a mantener todo alineado.
Contenedor codificado
3
Esta es mi respuesta preferida. Muy limpio y sencillo.
Hinrich
1
¡Funciona muy bien para mi implementación también! :)
Will Strohl
1
Genial ... ¡Esto debería votarse como una respuesta aceptada!
Velu
3
Esta es la respuesta perfecta
Deepu
148

Sé que es un poco tarde, pero aún podría ayudar a alguien. Lo hice así:

<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
fuente
3
realmente me ayudó mucho !! Gracias.
SupimpaAllTheWay
¡Eso es realmente fácil de implementar! ¡Gracias!
Manas Bajaj
Me ayudo mucho. ¡Gracias!
Ahmad Ajmi
17
Gran solución, sin embargo, no comprueba si $ index + 1 y $ index +2 están más allá de los límites de la matriz. Los dos últimos divs requieren ng-if="$index+1 < products.length"yng-if="$index+2 < products.length"
Mustafa Ozturk
¿Podemos hacer esto para el par clave, valor? obteniendo la siguiente clave? En lugar de obtener $ index + 1
bpbhat77
25

De acuerdo, esta solución es mucho más simple que las que ya están aquí y permite diferentes anchos de columna para diferentes anchos 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>

Tenga en cuenta que % 6se supone que la parte es igual al número de columnas resultantes. Entonces, si en el elemento de columna tiene la clase col-lg-2, habrá 6 columnas, así que use... % 6 .

Esta técnica (excluyendo la ng-if) está realmente documentada aquí: Bootstrap docs


fuente
1
En mi opinión, esta es la mejor solución.
user216661
Si es nuevo en bootstrap, es fácil pasar por alto el hecho de que no es necesario definir las filas. Esto funcionó perfectamente y es una versión un poco más completa de la solución de Duncan.
phosplait
Esto es exactamente lo que estaba buscando.
Hinrich
@phosplait ¿Cuál es la ventaja de esto sobre Duncan?
Jeppe
17

Si bien lo que desea lograr puede ser útil, hay otra opción que creo que podría estar pasando por alto y que es mucho más simple.

Tiene razón, las tablas Bootstrap actúan de manera extraña cuando tiene columnas que no tienen una altura fija. Sin embargo, existe una clase de arranque creada para combatir este problema y realizar reinicios sensibles .

simplemente cree un vacío <div class="clearfix"></div>antes del inicio de cada nueva fila para permitir que los flotadores se reinicien y las columnas vuelvan a sus posiciones correctas.

aquí hay un bootply .

Claies
fuente
Esto no resuelve los 15px negativos de margen que tiene cada .row para bootstrap.
Logus
¿Funciona esto flexpara que las columnas tengan la misma altura?
Alisson
16

Gracias por tus sugerencias, ¡me pusiste en el camino correcto!

Vayamos por una explicación completa:

  • Por defecto AngularJS http get query devuelve un objeto

  • Entonces, si desea utilizar la función @Ariel Array.prototype.chunk, primero debe transformar el objeto en una matriz.

  • Y luego usar la función chunk EN SU CONTROLADOR; de lo contrario, si se usa directamente en ng-repeat, obtendrá un error de infdig . El controlador final se ve:

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

Y HTML se convierte en:

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

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

Por otro lado, decidí devolver directamente una matriz [] en lugar de un objeto {} de mi archivo JSON. De esta manera, el controlador se convierte (tenga en cuenta la sintaxis 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 sigue siendo el mismo que el anterior.

MEJORAMIENTO

La última pregunta en suspenso es: cómo hacerlo 100% AngularJS sin extender la matriz de javascript con la función chunk ... si algunas personas están interesadas en mostrarnos si ng-repeat-start y ng-repeat-end son el camino a seguir ... . Soy curioso ;)

LA SOLUCIÓN DE ANDREW

Gracias a @Andrew, ahora sabemos que agregar una clase de arranque clearfix cada tres (o cualquier número) elemento corrige el problema de visualización desde diferentes alturas de bloque.

Entonces HTML se convierte en:

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

Y su controlador se mantiene bastante suave con la función de chunck eliminada :

// 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;
        });
abrazos
fuente
7

Puede hacerlo sin una directiva, pero no estoy seguro de que sea la mejor manera. Para hacer esto, debe crear una matriz de matriz a partir de los datos que desea mostrar en la tabla, y luego use 2 ng-repeat para iterar a través de la matriz.

para crear la matriz para la visualización, use esta función como esa 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)];
        })
    );
}

y luego haz algo así 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
fuente
7

Basado en la solución de Alpar, utilizando solo plantillas con repetición ng anidada. Funciona con filas llenas y parcialmente vacías:

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

Acabo de hacer una solución que funciona solo en plantilla. La solucion es

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

El punto está usando datos dos veces, uno es para un bucle externo. Las etiquetas de tramo adicionales permanecerán, pero depende de cómo las intercambie.

Si es un diseño de 3 columnas, 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 yo quería

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

Aunque no funcionó.

wataru
fuente
Probé esta solución para implementar 2 elementos en cada fila. Por ejemplo, tengo 5 elementos en una lista, por lo que la salida que debería tener es 3 filas con 2 elementos / columnas en las primeras 2 filas y 1 columna en la última fila. El problema es que obtengo 5 filas aquí con las últimas 2 filas vacías. ¿Se pregunta cómo solucionar esto? Gracias
Maverick Riz
@MaverickAzy gracias por intentarlo. Sé que existe el problema de que si la altura de esos elementos es diferente, no funciona bien.
wataru
La altura de los elementos es la misma. El problema es que debería obtener solo 3 filas pero obtener 5 filas con las últimas 2 filas vacías. ¿Puede decirme si products.length es 5, entonces 5/2 + 1 =? Esta lógica no está clara para mí en la línea número 2 para la fila de clase.
Maverick Riz
@MaverickAzy esas filas vacías deben generarse como están. ¿Ensucia tu diseño?
wataru
no, no ensucia el diseño. La única preocupación es con respecto a las filas vacías. Realmente agradecería si me puede ayudar con esto. Gracias
Maverick Riz
5

Solo otra pequeña mejora sobre la respuesta de @Duncan y las otras respuestas basadas en el elemento clearfix. Si desea que se pueda hacer clic en el contenido , necesitará unz-index > 0 o clearfix superpondrá el contenido y manejará el clic.

Este es el ejemplo que no funciona (no puede ver el puntero del cursor y hacer clic no hará 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>

Si bien este es el fijo :

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

He añadido z-index: 1a tener el aumento de contenido a través de clearfix y he quitado el contenedor div utilizando en su lugar ng-repeat-startyng-repeat-end (disponible en AngularJS 1.2) porque hizo z-index no funciona.

¡Espero que esto ayude!

Actualizar

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

McGiogen
fuente
¿Funciona esto flexen filas para que las columnas tengan la misma altura?
Alisson
No estoy seguro de entender tu pregunta. Este es un plunker rápido para mostrarle lo que hace este código: plnkr.co/edit/4w5wZj?p=preview . En palabras, clearfix alinea correctamente la segunda línea de títulos: todos parten del mismo punto pero todavía no tienen la misma altura (como puedes ver gracias al color de fondo). Intente eliminar la clase clearfix para ver cuál es el comportamiento predeterminado. He usado flexbox solo una o dos veces, pero tiene muchas propiedades CSS y estoy seguro de que puede encontrar lo que está buscando.
McGiogen
bootstrap proporciona un ejemplo sobre cómo hacer que todas las columnas estén en la misma fila para obtener la misma altura de la columna más alta. Tuve que usar esto. El problema es que pierde la capacidad de ajustarse a una nueva línea cuando hay más de 12 columnas, por lo que debe crear manualmente nuevas filas. Después de investigar un poco más, pude obtener una solución y publicarla aquí como respuesta, aunque no sé si es la mejor. Gracias de todos modos, tu respuesta me ayudó!
Alisson
Es la primera vez que veo ese ejemplo, ¡es realmente útil! Encantado de ayudarle.
McGiogen
4

Resolví esto 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
fuente
2

La mejor manera de aplicar una clase es usar ng-class, que se puede usar para aplicar clases en función de alguna condición.

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

y luego en tu controlador

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

Después de combinar muchas respuestas y sugerencias aquí, esta es mi respuesta final, que funciona bien con flex, que nos permite hacer columnas con la misma altura, también verifica el último índice y no es necesario repetir el HTML interno. No 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>

Producirá algo como esto:

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

Pequeña modificación en la solución 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
fuente
0

Esto funcionó para mí, sin empalmes ni nada requerido:

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

Born Solutions es la mejor, solo necesito un poco de ajuste para satisfacer las necesidades, tuve diferentes soluciones de respuesta y cambié un poco

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

Sobre la base de la respuesta de Alpar, aquí hay una forma más generalizada de dividir una sola lista de elementos en varios contenedores (filas, columnas, cubos, lo que sea):

<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 una lista de 10 elementos, genera:

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

El número de contenedores se puede codificar rápidamente en una función 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">
    ...

Este enfoque evita duplicar el marcado del elemento ( <span>{{item.name}}</span>en este caso) en la plantilla de origen; no es una gran victoria para un tramo simple, pero para una estructura DOM más compleja (que tenía) esto ayuda a mantener la plantilla SECA.

Mark Chitty
fuente
0

Actualización 2019 - Bootstrap 4

Dado que Bootstrap 3 usaba flotantes, requería reiniciar clearfix cada n (3 o 4) columnas ( .col-*) en el.row para evitar un ajuste desigual de las columnas.

Ahora que Bootstrap 4 usa flexbox , ya no es necesario envolver columnas en .rowetiquetas separadas , o insertar divs adicionales para forzar a cols a envolver cada n columnas.

Simplemente puede repetir todas las columnas en un solo .rowcontenedor.

Por ejemplo, 3 columnas en cada fila visual es:

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

Entonces, para Bootstrap, ng-repeat es simplemente:

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

Demostración: https://www.codeply.com/go/Z3IjLRsJXX

Zim
fuente
0

Lo hice solo usando boostrap, debes tener mucho cuidado en la ubicación de la fila y la columna, aquí está mi ejemplo.

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