Angular ng-repeat ajouter une ligne bootstrap tous les 3 ou 4 cols

111

Je recherche le bon modèle pour injecter une classe de ligne bootstrap toutes les 3 colonnes. J'en ai besoin parce que les cols n'ont pas de hauteur fixe (et je ne veux pas en corriger une), donc ça casse ma conception!

Voici mon code:

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

Mais il n'affiche qu'un seul produit dans chaque ligne. Ce que je veux comme résultat final, c'est:

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

Puis-je y parvenir avec uniquement un motif ng-repeat (sans directive ni contrôleur)? La documentation présente ng-repeat-start et ng-repeat-end mais je ne peux pas comprendre comment l'utiliser dans ce cas d'utilisation! J'ai l'impression que c'est quelque chose que nous utilisons souvent dans la création de modèles bootstrap! ? Merci

câlins
la source
Je pense que vous devriez modéliser vos données d'une manière qui correspond à votre conception, il devrait probablement s'agir d'un tableau ou d'un objet multidimensionnel, avec une représentation des lignes et des colonnes, puis vous devriez itérer sur les lignes et utiliser la directive de classe conditionnelle "ng-class" et la ligne intérieure vous devez ensuite parcourir les colonnes.
antanas_sepikas
Intéressant et certainement une solution de travail mais le jour où je veux afficher 4 produits par ligne au lieu de 3, j'ai, pour modifier ma structure de données, je préférerais que cela reste dans le "champ" de la fonctionnalité d'affichage pure ...
hugsbrugs
Je vois, alors vous devriez probablement itérer par morceaux comme dans la réponse d'Ariel, vous pouvez également trouver ce post stackoverflow.com/questions/18564888/… utile.
antanas_sepikas
Je pense que c'est exactement ce que vous recherchez: stackoverflow.com/a/30426750/1943442
user1943442

Réponses:

164

La réponse la plus votée, bien qu'efficace, n'est pas ce que je considérerais comme la voie angulaire, ni n'utilise les propres classes de bootstrap qui sont destinées à faire face à cette situation. Comme @claies l'a mentionné, la .clearfixclasse est destinée à de telles situations. À mon avis, la mise en œuvre la plus propre est la suivante:

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

Cette structure évite une indexation désordonnée du tableau products, permet une notation par points propre et utilise la classe clearfix pour son usage prévu.

Duncan
la source
6
C'est une bonne idée, cependant si vous êtes intéressé par l'utilisation de flexbox, vous devez l'utiliser sur la ligne et non sur les div à l'intérieur des lignes afin de permettre à chaque boîte / div d'avoir la même hauteur. Clearfix est génial mais n'aide pas à tout garder aligné.
Conteneur codé
3
C'est ma réponse préférée. Très propre et facile.
Hinrich
1
Fonctionne très bien pour ma mise en œuvre aussi! :)
Will Strohl
1
Super ... Cela devrait être voté comme une réponse acceptée!
Velu
3
C'est la réponse parfaite
Deepu
148

Je sais qu'il est un peu tard, mais cela pourrait encore aider quelqu'un. Je l'ai fait comme ça:

<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
la source
3
ça m'a beaucoup aidé !! Merci.
SupimpaAllTheWay
C'est vraiment facile à mettre en œuvre! Merci!
Manas Bajaj du
M'a beaucoup aidé. Merci!
Ahmad Ajmi
17
Excellente solution, mais elle ne vérifie pas si $ index + 1 et $ index +2 dépassent les limites du tableau. Les deux dernières divs nécessitent ng-if="$index+1 < products.length"etng-if="$index+2 < products.length"
Mustafa Ozturk
Pouvons-nous faire cela pour une paire clé / valeur. obtenir la clé suivante? Au lieu d'obtenir $ index + 1
bpbhat77
25

D'accord, cette solution est beaucoup plus simple que celles déjà présentes et permet différentes largeurs de colonne pour différentes largeurs de périphérique.

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

Notez que la % 6pièce est censée être égale au nombre de colonnes résultantes. Donc, si sur l'élément de colonne vous avez la classe, col-lg-2il y aura 6 colonnes, alors utilisez ... % 6.

Cette technique (à l'exclusion du ng-if) est en fait documentée ici: Bootstrap docs


la source
1
À mon avis, c'est la meilleure solution.
user216661
Si vous êtes nouveau dans bootstrap, il est facile d'oublier le fait que la définition des lignes n'est pas nécessaire. Cela a parfaitement fonctionné et constitue une version légèrement plus complète de la solution de Duncan.
phosplait
Ceci est exactement ce que je cherchais.
Hinrich
@phosplait Quel est l'avantage de ceci par rapport à celui de Duncan?
Jeppe
17

Bien que ce que vous voulez accomplir puisse être utile, il existe une autre option que vous ignorez peut-être, je crois, qui est beaucoup plus simple.

Vous avez raison, les tables Bootstrap agissent étrangement lorsque vous avez des colonnes qui ne sont pas de hauteur fixe. Cependant, il existe une classe d'amorçage créée pour lutter contre ce problème et effectuer des réinitialisations réactives .

créez simplement un vide <div class="clearfix"></div>avant le début de chaque nouvelle ligne pour permettre aux flotteurs de se réinitialiser et aux colonnes de revenir à leurs positions correctes.

voici un bootply .

Claies
la source
Cela ne résout pas les 15px de marge négatifs que chaque .row a pour le bootstrap.
Logus
Cela fonctionne-t-il avec flexpour que les colonnes aient la même hauteur?
Alisson
16

Merci pour vos suggestions, vous m'avez mis sur la bonne voie!

Allons-y pour une explication complète:

  • Par défaut, AngularJS http get query renvoie un objet

  • Donc, si vous voulez utiliser la fonction @Ariel Array.prototype.chunk, vous devez d'abord transformer l'objet en tableau.

  • Et puis pour utiliser la fonction chunk DANS VOTRE CONTRÔLEUR sinon si elle est utilisée directement dans ng-repeat, cela vous amènera à une erreur infdig . Le contrôleur final ressemble:

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

Et le HTML devient:

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

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

De l'autre côté, j'ai décidé de retourner directement un tableau [] au lieu d'un objet {} de mon fichier JSON. De cette façon, le contrôleur devient (veuillez noter la syntaxe spécifique 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);
    });

Le HTML reste le même que ci-dessus.

OPTIMISATION

Dernière question en suspens: comment le rendre 100% AngularJS sans étendre le tableau javascript avec la fonction chunk ... si certaines personnes sont intéressées à nous montrer si ng-repeat-start et ng-repeat-end sont la voie à suivre .. . Je suis curieux ;)

LA SOLUTION D'ANDREW

Grâce à @Andrew, nous savons maintenant que l'ajout d'une classe bootstrap clearfix tous les trois éléments (ou n'importe quel nombre) corrige le problème d'affichage à partir de différentes hauteurs de bloc.

Le HTML devient donc:

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

Et votre contrôleur reste assez doux avec la fonction chunck supprimée :

// 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;
        });
câlins
la source
7

Vous pouvez le faire sans directive, mais je ne suis pas sûr que ce soit la meilleure façon. Pour ce faire, vous devez créer un tableau de tableau à partir des données que vous souhaitez afficher dans le tableau, puis utiliser 2 ng-repeat pour parcourir le tableau.

pour créer le tableau à afficher, utilisez cette fonction comme celle-ci 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)];
        })
    );
}

puis faites quelque chose comme ça en utilisant 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
la source
7

Basé sur la solution Alpar, en utilisant uniquement des modèles avec ng-repeat anidated. Fonctionne avec les lignes pleines et partiellement vides:

<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
la source
5

Je viens d'en faire une solution fonctionnant uniquement dans le modèle. La solution est

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

Le point utilise des données deux fois, un pour une boucle extérieure. Les balises de portée supplémentaire resteront, mais cela dépend de la façon dont vous faites un compromis.

Si c'est une mise en page à 3 colonnes, ça va être comme

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

Honnêtement je voulais

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

Bien que cela n'ait pas fonctionné.

Wataru
la source
J'ai essayé cette solution pour implémenter 2 éléments dans chaque ligne. Par exemple, j'ai 5 éléments dans une liste, donc la sortie que je devrais avoir est de 3 lignes avec 2 éléments / colonnes dans les 2 premières lignes et 1 colonne dans la dernière ligne. Le problème est que j'obtiens 5 lignes ici avec les 2 dernières lignes vides. Vous vous demandez comment résoudre ce problème? Merci
Maverick Riz
@MaverickAzy merci d'avoir essayé. Je sais qu'il y a un problème si la hauteur de ces éléments est différente, cela ne fonctionne pas bien.
wataru
La hauteur des éléments est en fait la même. Le problème est que je ne devrais obtenir que 3 lignes mais obtenir 5 lignes avec les 2 dernières lignes vides. Pouvez-vous me dire si products.length est de 5, alors 5/2 + 1 =? Cette logique n'est pas claire pour moi à la ligne n ° 2 pour la ligne de classe.
Maverick Riz
@MaverickAzy ces lignes vides doivent être générées telles quelles. Cela gâche votre mise en page?
wataru
non, ça ne gâche pas la mise en page. Le seul souci concerne les lignes vides. J'apprécie vraiment si vous pouvez m'aider avec cela. Merci
Maverick Riz
5

Juste une autre petite amélioration concernant la réponse @Duncan et les autres réponses basées sur l'élément clearfix. Si vous souhaitez rendre le contenu cliquable, vous aurez besoin d'un z-index> 0 dessus, sinon clearfix chevauchera le contenu et gérera le clic.

Cet exemple ne fonctionne pas (vous ne pouvez pas voir le pointeur du curseur et un clic ne fera rien):

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

Bien que ce soit le fixe :

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

J'ai ajouté z-index: 1pour que le contenu augmente sur le clearfix et j'ai supprimé le conteneur div en utilisant à la place ng-repeat-startet ng-repeat-end(disponible à partir d'AngularJS 1.2) car cela empêchait z-index de fonctionner.

J'espère que cela t'aides!

Mettre à jour

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

McGiogen
la source
Cela fonctionne-t-il avec les flexlignes pour que les colonnes aient la même hauteur?
Alisson
Je ne suis pas sûr de comprendre votre question. C'est un outil rapide pour vous montrer ce que fait ce code: plnkr.co/edit/4w5wZj?p=preview . En clair, clearfix aligne correctement la deuxième ligne de titres: ils partent tous du même point mais ils n'ont toujours pas la même hauteur (comme vous pouvez le voir grâce à la couleur de fond). Essayez de supprimer la classe clearfix pour voir quel est le comportement par défaut. Je n'ai utilisé flexbox qu'une ou deux fois, mais il a beaucoup de propriétés css et je suis sûr que vous pouvez trouver ce que vous cherchez.
McGiogen
bootstrap fournit un exemple sur la façon de créer toutes les colonnes dans la même ligne pour obtenir la même hauteur de la colonne la plus haute. Je devais utiliser ça. Le problème est qu'il perd la capacité de s'enrouler sur une nouvelle ligne lorsqu'il y a plus de 12 colonnes, vous devez donc créer manuellement de nouvelles lignes. Après avoir recherché un peu plus, je pourrais obtenir une solution et publier ici comme réponse, même si je ne sais pas si c'est la meilleure. Merci quand même, votre réponse m'a aidé!
Alisson
C'est la première fois que je vois cet exemple, c'est vraiment utile! Heureux de vous aider.
McGiogen
4

J'ai résolu cela en utilisant 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
la source
2

La meilleure façon d'appliquer une classe est d'utiliser ng-class.Il peut être utilisé pour appliquer des classes en fonction de certaines conditions.

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

puis dans votre contrôleur

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

Après avoir combiné de nombreuses réponses et suggestions ici, voici ma réponse finale, qui fonctionne bien avec flex, qui nous permet de créer des colonnes de hauteur égale, elle vérifie également le dernier index et vous n'avez pas besoin de répéter le HTML interne. Il n'utilise pas 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>

Il affichera quelque chose comme ceci:

<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
la source
1

Petite modification dans la solution 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
la source
0

Cela a fonctionné pour moi, aucun épissage ou quoi que ce soit requis:

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
la source
0

Born Solutions, c'est le meilleur, j'ai juste besoin d'un peu de temps pour répondre aux besoins, j'avais différentes solutions réactives et j'ai un peu changé

<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
la source
0

Sur la base de la réponse d'Alpar, voici un moyen plus généralisé de diviser une seule liste d'éléments en plusieurs conteneurs (lignes, colonnes, seaux, etc.):

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

pour une liste de 10 éléments, génère:

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

Le nombre de conteneurs peut être rapidement codé dans une fonction de contrôleur:

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

Cette approche évite de dupliquer le balisage de l'élément ( <span>{{item.name}}</span>dans ce cas) dans le modèle source - pas une énorme victoire pour un simple span, mais pour une structure DOM plus complexe (que j'avais), cela aide à garder le modèle DRY.

Mark Chitty
la source
0

Mise à jour 2019 - Bootstrap 4

Étant donné que Bootstrap 3 utilisait des flottants, il était nécessaire de réinitialiser clearfix toutes les n (3 ou 4) colonnes ( .col-*) dans le .rowpour éviter un habillage irrégulier des colonnes.

Maintenant que Bootstrap 4 utilise flexbox , il n'est plus nécessaire d'envelopper les colonnes dans des .rowbalises séparées , ou d'insérer des div supplémentaires pour forcer les cols à envelopper toutes les n colonnes.

Vous pouvez simplement répéter toutes les colonnes en un seul.row conteneur.

Par exemple, 3 colonnes dans chaque ligne visuelle sont:

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

Donc, pour Bootstrap, le ng-repeat est simplement:

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

Démo: https://www.codeply.com/go/Z3IjLRsJXX

Zim
la source
0

Je ne l'ai fait qu'en utilisant boostrap, il faut faire très attention à l'emplacement de la ligne et de la colonne, voici mon exemple.

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