J'utilise ng-repeat avec mon code. J'ai un numéro de zone de texte basé sur ng-repeat. Je veux aligner la zone de texte avec trois colonnes.
c'est mon code
<div class="control-group" ng-repeat="oneExt in configAddr.ext">
{{$index+1}}.
<input type="text" name="macAdr{{$index+1}}"
id="macAddress" ng-model="oneExt.newValue" value=""/>
</div>
L'approche la plus fiable et techniquement correcte consiste à transformer les données dans le contrôleur. Voici une fonction de morceau simple et son utilisation.
function chunk(arr, size) {
var newArr = [];
for (var i=0; i<arr.length; i+=size) {
newArr.Push(arr.slice(i, i+size));
}
return newArr;
}
$scope.chunkedData = chunk(myData, 3);
Votre vue ressemblerait alors à ceci:
<div class="row" ng-repeat="rows in chunkedData">
<div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>
Si vous avez des entrées dans le ng-repeat
, vous voudrez probablement déverrouiller/rejoindre les tableaux lorsque les données sont modifiées ou soumises. Voici comment cela se présenterait dans un $watch
, de sorte que les données soient toujours disponibles dans le format d'origine fusionné:
$scope.$watch('chunkedData', function(val) {
$scope.data = [].concat.apply([], val);
}, true); // deep watch
Beaucoup de gens préfèrent accomplir cela dans la vue avec un filtre. Ceci est possible, mais ne devrait être utilisé qu'à des fins d'affichage! Si vous ajoutez des entrées dans cette vue filtrée, cela posera des problèmes qui peuvent être résolus, mais ne sont ni jolis ni fiables.
Le problème avec ce filtre est qu’il renvoie à chaque fois de nouveaux tableaux imbriqués. Angular surveille la valeur de retour du filtre. Angular connaît la valeur lors de la première utilisation du filtre, puis l'exécute à nouveau pour s'assurer qu'elle est modifiée. Si les deux valeurs sont identiques, le cycle est terminé. Si ce n'est pas le cas, le filtre se déclenchera encore et encore jusqu'à ce qu'ils soient identiques, ou Angular réalisera qu'une boucle de digestion infinie se produit et s'arrête. Etant donné que les nouveaux tableaux/objets imbriqués n’avaient pas été suivis auparavant par Angular, la valeur renvoyée est toujours considérée comme différente de la valeur précédente. Pour corriger ces filtres "instables", vous devez envelopper le filtre dans une fonction memoize
. lodash
a une fonction memoize
et la dernière version de lodash inclut également une fonction chunk
. Nous pouvons donc créer ce filtre très simplement en utilisant les modules npm
et en compilant le script avec browserify
ou webpack
.
N'oubliez pas: afficher uniquement! Filtre dans le contrôleur si vous utilisez des entrées!
Installez lodash:
npm install lodash-node
Créez le filtre:
var chunk = require('lodash-node/modern/array/chunk');
var memoize = require('lodash-node/modern/function/memoize');
angular.module('myModule', [])
.filter('chunk', function() {
return memoize(chunk);
});
Et voici un exemple avec ce filtre:
<div ng-repeat="row in ['a','b','c','d','e','f'] | chunk:3">
<div class="column" ng-repeat="item in row">
{{($parent.$index*row.length)+$index+1}}. {{item}}
</div>
</div>
1 4
2 5
3 6
En ce qui concerne les colonnes verticales (liste de haut en bas) et non horizontales (de gauche à droite), l'implémentation exacte dépend de la sémantique souhaitée. Les listes qui se divisent de manière inégale peuvent être réparties de différentes manières. Voici un moyen:
<div ng-repeat="row in columns">
<div class="column" ng-repeat="item in row">
{{item}}
</div>
</div>
var data = ['a','b','c','d','e','f','g'];
$scope.columns = columnize(data, 3);
function columnize(input, cols) {
var arr = [];
for(i = 0; i < input.length; i++) {
var colIdx = i % cols;
arr[colIdx] = arr[colIdx] || [];
arr[colIdx].Push(input[i]);
}
return arr;
}
Cependant, le moyen le plus direct et le plus simple d'obtenir des colonnes est d'utiliser CSS columns :
.columns {
columns: 3;
}
<div class="columns">
<div ng-repeat="item in ['a','b','c','d','e','f','g']">
{{item}}
</div>
</div>
Cette solution est très simple:
JSON:
[{id:"1",name:"testA"},{id:"2",name:"test B"},{id:"3",name:"test C"},{id:"4",name:"test D"},{id:"5",name:"test E"}]
HTML:
<div ng-controller="MyCtrl">
<table>
<tr ng-repeat="item in items" ng-switch on="$index % 3">
<td ng-switch-when="0">
{{items[$index].id}} {{items[$index].name}}
</td>
<td ng-switch-when="0">
<span ng-show="items[$index+1]">
{{items[$index+1].id}} {{items[$index+1].name}}
</span>
</td>
<td ng-switch-when="0">
<span ng-show="items[$index+2]">
{{items[$index+2].id}} {{items[$index+2].name}}
</span>
</td>
</tr>
</table>
</div>
Une solution propre et adaptable qui ne nécessite pas de manipulation de données:
Le HTML:
<div class="control-group" class="label"
ng-repeat="oneExt in configAddr.ext"
ng-class="{'new-row': startNewRow($index, columnBreak) }">
{{$index+1}}.
<input type="text" name="macAdr{{$index+1}}"
id="macAddress{{$index}}" ng-model="oneExt.newValue" />
</div>
Le CSS:
.label {
float: left;
text-align: left;
}
.new-row {
clear: left;
}
Le JavaScript:
$scope.columnBreak = 3; //Max number of colunms
$scope.startNewRow = function (index, count) {
return ((index) % count) === 0;
};
Il s'agit d'une solution simple pour le rendu dynamique des données en lignes et en colonnes sans qu'il soit nécessaire de manipuler le tableau de données que vous essayez d'afficher. De plus, si vous essayez de redimensionner la fenêtre du navigateur, vous pouvez voir que la grille s’adapte dynamiquement à la taille de l’écran/div.
(J'ai également ajouté un suffixe {{$ index}} à votre identifiant car ng-repeat essayera de créer plusieurs éléments avec le même identifiant si vous ne le faites pas.)
la réponse de m59 est plutôt bonne. La seule chose que je n'aime pas à ce sujet, c'est qu'il utilise div
s pour ce qui pourrait être des données pour une table.
Donc, en conjonction avec le filtre de m59 (réponse quelque part ci-dessus), voici comment l'afficher dans un tableau.
<table>
<tr class="" ng-repeat="rows in foos | chunk:2">
<td ng-repeat="item in rows">{{item}}</td>
</tr>
</table>
Voici un moyen plus simple:
<table>
<tr ng-repeat="item in lists" ng-hide="$index%2!==0">
<td>
<label>{{ lists[$index].name}}</label>
</td>
<td ng-hide="!lists[$index+1]">
<label>{{ lists[$index+1].name}}</label>
</td>
</tr>
</table>
La réponse de Cumulo Nimbus m'est utile, mais je veux que cette grille soit entourée d'un div qui puisse afficher la barre de défilement lorsque la liste est trop longue.
Pour ce faire, j'ai ajouté style="height:200px; overflow:auto"
à un div autour de la table, ce qui l'a fait apparaître sous la forme d'une colonne unique.
Fonctionne maintenant pour une longueur de tableau de un.
J'ai une fonction et l'ai stockée dans un service pour que je puisse l'utiliser dans mon application:
Un service:
app.service('SplitArrayService', function () {
return {
SplitArray: function (array, columns) {
if (array.length <= columns) {
return [array];
};
var rowsNum = Math.ceil(array.length / columns);
var rowsArray = new Array(rowsNum);
for (var i = 0; i < rowsNum; i++) {
var columnsArray = new Array(columns);
for (j = 0; j < columns; j++) {
var index = i * columns + j;
if (index < array.length) {
columnsArray[j] = array[index];
} else {
break;
}
}
rowsArray[i] = columnsArray;
}
return rowsArray;
}
}
});
Manette:
$scope.rows = SplitArrayService.SplitArray($scope.images, 3); //im splitting an array of images into 3 columns
Balisage:
<div class="col-sm-12" ng-repeat="row in imageRows">
<div class="col-sm-4" ng-repeat="image in row">
<img class="img-responsive" ng-src="{{image.src}}">
</div>
</div>
Un truc simple avec CSS "clearfix" recommandé par Bootstrap:
<div class="row">
<div ng-repeat-start="value in values" class="col-md-4">
{{value}}
</div>
<div ng-repeat-end ng-if="$index % 3 == 0" class="clearfix"></div>
</div>
De nombreux avantages: Efficace, rapide, en utilisant les recommandations Boostrap, en évitant les problèmes de $ digest, et en ne modifiant pas le modèle angulaire.
Mon approche était un mélange de choses.
Mon objectif était d'avoir une grille qui s'adapte à la taille de l'écran. Je voulais 3 colonnes pour lg
, 2 colonnes pour sm
et md
et 1 colonne pour xs
.
Tout d'abord, j'ai créé la fonction de portée suivante, à l'aide du service angulaire $window
:
$scope.findColNumberPerRow = function() {
var nCols;
var width = $window.innerWidth;
if (width < 768) {
// xs
nCols = 1;
}
else if(width >= 768 && width < 1200) {
// sm and md
nCols = 2
} else if (width >= 1200) {
// lg
nCols = 3;
}
return nCols;
};
Ensuite, j'ai utilisé le cours proposé par @Cumulo Nimbus:
.new-row {
clear: left;
}
Dans la div contenant le ng-repeat
, j'ai ajouté la directive resizable
, comme expliqué dans cette page , afin que chaque fenêtre temporelle soit redimensionnée, le service angulaire $window
soit mis à jour avec les nouvelles valeurs.
En fin de compte, dans la division répétée, j'ai:
ng-repeat="user in users" ng-class="{'new-row': ($index % findColNumberPerRow() === 0) }"
S'il vous plaît, laissez-moi savoir les défauts de cette approche.
J'espère que cela peut être utile.
Voici un moyen facile de le faire. C'est plus manuel et finit avec un balisage désordonné. Je ne le recommande pas, mais cela peut être utile dans certaines situations.
Voici un lien de violon http://jsfiddle.net/m0nk3y/9wcbpydq/
HTML:
<div ng-controller="myController">
<div class="row">
<div class="col-sm-4">
<div class="well">
<div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3)">
{{ person.name }}
</div>
</div>
</div>
<div class="col-sm-4">
<div class="well">
<div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3):Math.ceil(data.length/3)">
{{ person.name }}
</div>
</div>
</div>
<div class="col-sm-4">
<div class="well">
<div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3):Math.ceil(data.length/3)*2">
{{ person.name }}
</div>
</div>
</div>
</div>
</div>
JS:
var app = angular.module('myApp', []);
app.controller('myController', function($scope) {
$scope.Math = Math;
$scope.data = [
{"name":"Person A"},
...
];
});
Cette configuration nécessite l'utilisation de Math sur le balisage: /, vous devrez donc injecter Math en ajoutant cette ligne: $scope.Math = Math;
Cet exemple produit un répéteur imbriqué dans lequel les données externes incluent un tableau interne que je voulais répertorier dans deux colonnes. Le concept serait valable pour trois colonnes ou plus.
Dans la colonne intérieure, je répète la "ligne" jusqu'à ce que la limite soit atteinte. La limite est déterminée en divisant la longueur du tableau d'éléments par le nombre de colonnes souhaité, dans ce cas par deux. La méthode de division repose sur le contrôleur et la longueur actuelle du tableau est transmise en tant que paramètre. La fonction JavaScript slice (0, array.length/columnCount) a ensuite appliqué la limite au répéteur.
Le deuxième répéteur de colonne est ensuite appelé et répète une tranche (array.length/columnCount, array.length) qui produit la seconde moitié du tableau de la deuxième colonne.
<div class="row" ng-repeat="GroupAccess in UsersController.SelectedUser.Access" ng-class="{even: $even, odd: $odd}">
<div class="col-md-12 col-xs-12" style=" padding-left:15px;">
<label style="margin-bottom:2px;"><input type="checkbox" ng-model="GroupAccess.isset" />{{GroupAccess.groupname}}</label>
</div>
<div class="row" style=" padding-left:15px;">
<div class="col-md-1 col-xs-0"></div>
<div class="col-md-3 col-xs-2">
<div style="line-height:11px; margin-left:2px; margin-bottom:2px;" ng-repeat="Feature in GroupAccess.features.slice(0, state.DivideBy2(GroupAccess.features.length))">
<span class="GrpFeature">{{Feature.featurename}}</span>
</div>
</div>
<div class="col-md-3 col-xs-2">
<div style="line-height:11px; margin-left:2px; margin-bottom:2px;" ng-repeat="Feature in GroupAccess.features.slice( state.DivideBy2(GroupAccess.features.length), GroupAccess.features.length )">
<span class="GrpFeature">{{Feature.featurename}}</span>
</div>
</div>
<div class="col-md-5 col-xs-8">
</div>
</div>
</div>
// called to format two columns
state.DivideBy2 = function(nValue) {
return Math.ceil(nValue /2);
};
J'espère que cela aide à regarder la solution d'une autre manière. (PS c'est mon premier post ici! :-))
Toutes ces réponses semblent excessivement élaborées.
De loin, la méthode la plus simple consiste à configurer les divs d’entrée dans un bootstrap de colonne col-md-4, puis bootstrap le formatera automatiquement en 3 colonnes en raison de la nature à 12 colonnes de bootstrap:
<div class="col-md-12">
<div class="control-group" ng-repeat="oneExt in configAddr.ext">
<div class="col-md-4">
<input type="text" name="macAdr{{$index}}"
id="macAddress" ng-model="oneExt.newValue" value="" />
</div>
</div>
</div>
Je répare sans .row
<div class="col col-33 left" ng-repeat="photo in photos">
Content here...
</div>
et css
.left {
float: left;
}
<div class="row">
<div class="col-md-4" ng-repeat="remainder in [0,1,2]">
<ul>
<li ng-repeat="item in items" ng-if="$index % 3 == remainder">{{item}}</li>
</ul>
</div>
</div>
Une autre façon est de définir width:33.33%; float:left
sur un div en wrapper comme ceci:
<div ng-repeat="right in rights" style="width: 33.33%;float: left;">
<span style="width:60px;display:inline-block;text-align:right">{{$index}}</span>
<input type="number" style="width:50px;display:inline-block" ">
</div>
Se baser sur la très bonne réponse de m59. J'ai trouvé que les entrées de modèle seraient floues si elles étaient modifiées. Vous ne pouvez donc modifier qu'un seul caractère à la fois. Ceci est un nouveau pour les listes d'objets pour ceux qui en ont besoin:
EDITMise à jour pour gérer plusieurs filtres sur une page
app.filter('partition', function() {
var cache = {}; // holds old arrays for difference repeat scopes
var filter = function(newArr, size, scope) {
var i,
oldLength = 0,
newLength = 0,
arr = [],
id = scope.$id,
currentArr = cache[id];
if (!newArr) return;
if (currentArr) {
for (i = 0; i < currentArr.length; i++) {
oldLength += currentArr[i].length;
}
}
if (newArr.length == oldLength) {
return currentArr; // so we keep the old object and prevent rebuild (it blurs inputs)
} else {
for (i = 0; i < newArr.length; i += size) {
arr.Push(newArr.slice(i, i + size));
}
cache[id] = arr;
return arr;
}
};
return filter;
});
Et ce serait l'usage:
<div ng-repeat="row in items | partition:3:this">
<span class="span4">
{{ $index }}
</span>
</div>
Je me suis retrouvé dans un cas similaire, voulant générer des groupes d'affichage de 3 colonnes chacun. Cependant, bien que j'utilisais bootstrap, j'essayais de séparer ces groupes en différentes div. Je voulais aussi faire quelque chose de génériquement utile.
Je l'ai approché avec 2 ng-repeat
comme ci-dessous:
<div ng-repeat="items in quotes" ng-if="!($index % 3)">
<div ng-repeat="quote in quotes" ng-if="$index <= $parent.$index + 2 && $index >= $parent.$index">
... some content ...
</div>
</div>
Cela rend très facile de passer à un nombre différent de colonnes et de les séparer en plusieurs div parents.
Lodash a une méthode de morceau intégrée maintenant que j'utilise personnellement: https://lodash.com/docs#chunk
Sur cette base, le code du contrôleur pourrait ressembler à ceci:
$scope.groupedUsers = _.chunk( $scope.users, 3 )
Voir le code:
<div class="row" ng-repeat="rows in groupedUsers">
<div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>
Je suis novice dans bootstrap et angularjs, mais cela pourrait aussi faire Array pour 4 éléments en tant que groupe, le résultat sera presque 3 colonnes . Cette astuce utilise le principe de la ligne de rupture bootstrap.
<div class="row">
<div class="col-sm-4" data-ng-repeat="item in items">
<div class="some-special-class">
{{item.XX}}
</div>
</div>
</div>