как разделить данные ng-repeat на три столбца с помощью начальной загрузки


122

Я использую ng-repeat со своим кодом. У меня есть n-е текстовое поле на основе ng-repeat. Я хочу выровнять текстовое поле с тремя столбцами.

это мой код

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

1
Итак, если у вас девять (9), вы хотите 1,2,3 в строке или в столбце?
Габриэле Петриоли

Ответы:


254

Самый надежный и технически правильный подход - преобразовать данные в контроллере. Вот простая функция и использование фрагментов.

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

Тогда ваше представление будет выглядеть так:

<div class="row" ng-repeat="rows in chunkedData">
  <div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>

Если у вас есть какие-либо входные данные в ng-repeat, вы, вероятно, захотите разделить / повторно объединить массивы по мере изменения данных или при отправке. Вот как это будет выглядеть в a $watch, чтобы данные всегда были доступны в исходном объединенном формате:

$scope.$watch('chunkedData', function(val) {
  $scope.data = [].concat.apply([], val);
}, true); // deep watch

Многие люди предпочитают делать это в представлении с фильтром. Это возможно, но должно использоваться только для демонстрации! Если вы добавите входные данные в это отфильтрованное представление, это вызовет проблемы, которые можно решить, но они не будут красивыми или надежными .

Проблема с этим фильтром в том, что он каждый раз возвращает новые вложенные массивы. Angular наблюдает за возвращаемым значением из фильтра. При первом запуске фильтра Angular знает значение, а затем запускает его снова, чтобы убедиться, что оно изменилось. Если оба значения совпадают, цикл завершается. В противном случае фильтр будет срабатывать снова и снова, пока они не станут одинаковыми, или Angular не поймет, что происходит бесконечный цикл дайджеста, и отключится. Поскольку новые вложенные массивы / объекты ранее не отслеживались Angular, он всегда видит возвращаемое значение как отличное от предыдущего. Чтобы исправить эти «нестабильные» фильтры, вы должны заключить фильтр в memoizeфункцию. lodashимеет memoizeфункцию, и последняя версия lodash также включает chunkфункцию, поэтому мы можем создать этот фильтр очень просто, используяnpmmodules и компилируем скрипт с помощью browserifyили webpack.

Помните: только отображение! Отфильтруйте контроллер, если вы используете входы!

Установите lodash:

npm install lodash-node

Создайте фильтр:

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

А вот образец с этим фильтром:

<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

Что касается вертикальных столбцов (список сверху вниз), а не горизонтальных (слева направо), точная реализация зависит от желаемой семантики. Списки, которые делятся неравномерно, можно распределить по-разному. Вот один из способов:

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

Однако наиболее прямой и простой способ получить столбцы - использовать столбцы CSS :

.columns {
  columns: 3;
}
<div class="columns">
  <div ng-repeat="item in ['a','b','c','d','e','f','g']">
    {{item}}
  </div>
</div>

Спасибо за вашу помощь. Но использование карусели с ui-chart в angular не может рисовать диаграмму bt carousel работает нормально. мой код - <carousel interval = "myInterval"> <slide ng-repeat = "веха в mileStone | split: 4" active = "slide.active"> <div class = "col-md-3" ng-repeat = " milestone1 in milestone "> <div style =" text-align: center; "> <div ui-chart =" data "chart-options =" ​​chartOptions "> </div> </div> </div> </ slide > </carousel> И я инициализировал свою переменную области как данные JSON этапа.
kuppu

1
@ m59 Прямо сейчас эта функция отображает данные в первой строке a b cвторой строки d e f. Могу ли я отображать a b в первом столбце c dво втором столбце и e fв третьем столбце? Спасибо

1
@ Ifch0o1 Вы были бы правы, если бы это было так [].concat.call([], val). Вы должны учитывать, что applyделает. val- это массив массивов, и мы хотим передать каждый из этих массивов внутри него в качестве аргументов concat. Я думаю, вы фокусируетесь на контексте [], но не в этом суть. Мы хотим [].concat(val[1], val[2], val[3], etc), значит, нам и нужноapply([], val)
m59

1
@bahadir Я обновил ответ, чтобы он был более ясным. Посмотрите, что произойдет, если вы создадите фильтр, который просто возвращает []vs [[]]и [{}]. Вложенный массив / объект заставляет Angular видеть разные результаты на каждом filterи продолжает повторять поиск того же результата (стабилизации).
m59,

1
Вы действительно спамите комментарии :) Попробуйте сами. var x = [1,2,3]; var y = [1,2,3]; console.log(x === y);Глубокая проверка означает либо преобразование в строку и сравнение строк, что рискованно, поскольку объект не может быть преобразован в строку, либо рекурсивную проверку каждого свойства по отдельности. Я думаю, что Angular может реализовать это для фильтров, но я уверен, что на то есть веская причина. Вероятно потому, что они в основном предназначались для фильтров для простой модификации набора данных, а не для полного пересмотра с изменениями в структуре.
m59,

64

Это решение очень простое:

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>

ДЕМО в FIDDLE

введите описание изображения здесь


3
Лучший ответ! И делайте то, что нужно, с родными директивами! Congratz!
Renan Franca

1
Это решение работает, но, судя по передовым практикам, вам, вероятно, не следует использовать таблицы для определения макета в вашей разметке :)
Дэйв Купер,

2
Как упоминает @DaveCooper, вот альтернативный макет без таблиц: plnkr.co/edit/81oj8mHaNyfQpCmRBWO4?p=preview . Рассмотрите таблицы для табличных данных, но в остальном это лучший макет для мобильных устройств и экранов переменного размера.
Zymotik

1
Это, безусловно, лучшее решение. Требуется время, чтобы найти все причуды при использовании нового фреймворка, но ng-switch - это находка,
Зоран

Если вы собираетесь использовать div вместо таблицы, вы можете разместить его ng-switch-when="0"во внешнем div один раз, а не на всех трех элементах.
Hans Wouters

19

Чистое, адаптируемое решение, не требующее манипулирования данными :

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>

CSS:

.label {
    float: left;
    text-align: left;
}
.new-row {
    clear: left;
}

JavaScript:

$scope.columnBreak = 3; //Max number of colunms
$scope.startNewRow = function (index, count) {
    return ((index) % count) === 0;
};

Это простое решение для динамического рендеринга данных в строки и столбцы без необходимости манипулировать массивом данных, которые вы пытаетесь отобразить. Кроме того, если вы попытаетесь изменить размер окна браузера, вы увидите, что сетка динамически адаптируется к размеру экрана / div.

(Я также добавил суффикс {{$ index}} к вашему идентификатору, поскольку ng-repeat попытается создать несколько элементов с одним и тем же идентификатором, если вы этого не сделаете.)

Аналогичный рабочий пример


1
Я считаю, что OP хотел разметку для строк / столбцов Twitter Bootstrap, но это может быть кому-то полезно. Однако в ответ вам необходимо указать код и пояснения. Ссылки только на ответы будут удалены.
m59,

@ m59 Обновленный ответ. Спасибо за предложение
Cumulo Nimbus

1
Большой +1 для не требует обработки данных . Это сработало идеально для моих нужд, когда я хотел разделить данные в диапазоне с помощью ng-repeat на 2 столбца, чтобы мой модальный Bootstrap не был таким длинным! Я был удивлен, насколько просто это было реализовать! <span ng-repeat="z in waiverModalLinks" ng-class="{'new-row':startNewRow($index, columnBreak)}" class="{{z.id}}"><a href="{{z.link}}{{z.title}}">{{z.title}}</a></span>Это внутри `модального тела '.
Christine268

15

Ответ m59 довольно хорош. Единственное, что мне не нравится в нем, так это то, что он использует divs для того, что потенциально может быть данными для таблицы.

Итак, в сочетании с фильтром m59 (ответ где-то выше), вот как отобразить его в таблице.

<table>
    <tr class="" ng-repeat="rows in foos | chunk:2">
        <td ng-repeat="item in rows">{{item}}</td>
    </tr>
</table>

7

Ниже приводится более простой способ:

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

Ответ Cumulo Nimbus полезен для меня, но я хочу, чтобы эта сетка была обернута div, который может отображать полосу прокрутки, когда список слишком длинный.

Чтобы добиться этого, я добавил style="height:200px; overflow:auto"к таблице div, который отображает его как отдельный столбец.

Теперь работает для массива длиной один.


когда в массиве всего 1 элемент, он не отображает саму первую строку. чтобы исправить это изменение ng-hide="$index%2!==0"
Anuj Pandey

4

У меня есть функция, и я сохранил ее в службе, чтобы использовать ее во всем приложении:

Обслуживание:

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

});

контроллер:

$scope.rows   = SplitArrayService.SplitArray($scope.images, 3); //im splitting an array of images into 3 columns

Разметка:

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

Это круто, но не помогает при использовании других фильтров, таких как orderBy или filter.
Робби Смит,

2

Мой подход был смесью вещей.

Моей целью было адаптировать сетку к размеру экрана. Я хотел 3 столбца для lg, 2 столбца для smи mdи 1 столбец для xs.

Сначала я создал следующую функцию области видимости, используя $windowслужбу angular :

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

Затем я использовал класс, предложенный @Cumulo Nimbus:

.new-row {
    clear: left;
}

В div, содержащий объект ng-repeat, я добавил resizableдирективу, как описано на этой странице , чтобы каждый раз при изменении размера окна $windowслужба angular обновлялась новыми значениями.

В конечном итоге в повторяющемся div у меня есть:

ng-repeat="user in users" ng-class="{'new-row': ($index % findColNumberPerRow() === 0) }"

Пожалуйста, сообщите мне о недостатках этого подхода.

Надеюсь, это может быть полезно.


2

Простой трюк с CSS clearfix, рекомендованный 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>

Множество преимуществ: эффективность, скорость, использование рекомендаций Boostrap, предотвращение возможных проблем с дайджестом и отсутствие изменения модели Angular.


1

В этом примере создается вложенный повторитель, внешние данные которого включают внутренний массив, который я хотел перечислить в двух столбцах. Эта концепция будет верна для трех или более столбцов.

Во внутреннем столбце повторяю «ряд» до тех пор, пока не будет достигнут предел. Предел определяется путем деления длины массива элементов на количество желаемых столбцов, в данном случае на два. Метод деления находится на контроллере, и ему передается текущая длина массива в качестве параметра. Затем функция JavaScript slice (0, array.length / columnCount) применила ограничение к повторителю.

Затем вызывается второй повторитель столбца, который повторяет срез (array.length / columnCount, array.length), который создает вторую половину массива во втором столбце.

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

Надеюсь, это поможет взглянуть на решение еще с одной стороны. (PS это мой первый пост здесь! :-))


1

Исправляю без .row

<div class="col col-33 left" ng-repeat="photo in photos">
   Content here...
</div>

и css

.left {
  float: left;
}

1

Вот простой способ сделать это. Это больше ручное и заканчивается грязной разметкой. Я не рекомендую это делать, но в некоторых ситуациях это может быть полезно.

Вот ссылка на скрипку 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"},
        ...
    ];
});

Эта установка требует, чтобы мы использовали математику в разметке: /, поэтому вам нужно ввести Math, добавив эту строку: $scope.Math = Math;


1

Все эти ответы кажутся чрезмерно продуманными.

Безусловно, самым простым методом было бы настроить входные блоки div в бутстрапе столбца col-md-4, тогда бутстрап автоматически отформатирует его в 3 столбца из-за 12-столбцового характера начальной загрузки:

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

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

0

Основываясь на очень хорошем ответе m59. Я обнаружил, что входные данные модели будут размыты, если они изменятся, поэтому вы можете изменять только один символ за раз. Это новый список объектов для всех, кому он нужен:

ИЗМЕНИТЬ Обновлено для обработки нескольких фильтров на одной странице

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

И это будет использование:

<div ng-repeat="row in items | partition:3:this">
  <span class="span4">
    {{ $index }}
  </span>
</div>

Насколько я могу судить, это должно работать большую часть времени, но эта логика кеширования не выглядит полностью надежной, и переход в область видимости неудобен. Я действительно рекомендую просто фильтровать внутри контроллера, когда требуются входы. В любом случае это технически правильный подход.
m59 05

0

Я новичок в bootstrap и angularjs, но это также может сделать массив на 4 элемента как одну группу, результат будет почти как 3 столбца. Этот трюк использует принцип разрыва начальной загрузки.

<div class="row">
 <div class="col-sm-4" data-ng-repeat="item in items">
  <div class="some-special-class">
   {{item.XX}}
  </div>
 </div>
</div>

0

Lodash теперь имеет встроенный метод фрагментов, который я использую лично: https://lodash.com/docs#chunk

Исходя из этого, код контроллера может выглядеть следующим образом:

$scope.groupedUsers = _.chunk( $scope.users, 3 )

Посмотреть код:

<div class="row" ng-repeat="rows in groupedUsers">
  <div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>

0

Другой способ - установить width:33.33%; float:leftdiv-обертку следующим образом:

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

0

Я оказался в аналогичном случае, когда хотел создать группы отображения по 3 столбца в каждой. Однако, хотя я использовал бутстрап, я пытался разделить эти группы на разные родительские div. Еще я хотел сделать что-то общее.

Я подошел к нему с 2, ng-repeatкак показано ниже:

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

Это позволяет очень легко перейти на другое количество столбцов и разделить их на несколько родительских div.


0

На всякий случай, если кому-то понадобится Angular 7 (и более поздняя версия), вот пример, который я использовал в одном из своих приложений:

HTML:

                   <div class="container-fluid">
                    <div class="row" *ngFor="let reports of chunkData; index as i">
                        <div *ngFor="let report of reports" class="col-4 col-sm-4"
                            style="border-style:solid;background-color: antiquewhite">{{report}}</div>
                    </div>
                </div>

.TS ФАЙЛ

export class ConfirmationPageComponent implements OnInit {
  chunkData = [];
  reportsArray = ["item 1", "item 2", "item 3", "item 4", "item 5", "item 6"];
  
  constructor() {}

  ngOnInit() {
    this.chunkData = this.chunk(this.reportsArray, 3);
  }

  chunk(arr, size) {
    var newArr = [];
    for (var i = 0; i < arr.length; i += size) {
      newArr.push(arr.slice(i, i + size));
    }
    return newArr;
  }
}

Это отличное решение для динамического создания новых столбцов / строк в зависимости от того, сколько элементов вы повторяете из базы данных. Спасибо!


-1

это отвечает на исходный вопрос: как получить 1,2,3 в столбце. - задан kuppu 08 фев.

angularjs код:

function GetStaffForFloor(floor) {
    var promiseGet = Directory_Service.getAllStaff(floor);
    promiseGet.then(function (pl) {
        $scope.staffList = chunk(pl.data, 3); //pl.data; //
    },
    function (errorOD) {
        $log.error('Errored while getting staff list.', errorOD);
    });
}
function chunk(array, columns) {
    var numberOfRows = Math.ceil(array.length / columns);

    //puts 1, 2, 3 into column
    var newRow = []; //array is row-based.
    for (var i = 0; i < array.length; i++) {
        var columnData = new Array(columns);
        if (i == numberOfRows) break;
        for (j = 0; j < columns; j++)
        {
            columnData[j] = array[i + numberOfRows * j];
        }
        newRow.push(columnData); 
    }
    return newRow;

    ////this works but 1, 2, 3 is in row
    //var newRow = [];
    //for (var i = 0; i < array.length; i += columns) {
    //    newRow.push(array.slice(i, i + columns)); //push effectively does the pivot. array is row-based.
    //}
    //return newRow;
};

Просмотреть код (примечание: с использованием bootstrap 3):

<div class="staffContainer">
    <div class="row" ng-repeat="staff in staffList">
        <div class="col-md-4" ng-repeat="item in staff">{{item.FullName.length > 0 ? item.FullName + ": Rm " + item.RoomNumber : ""}}</div>
    </div>
</div>

-1

Этот код поможет выровнять элементы с тремя столбцами в режиме lg и md, двумя столбцами в режиме sm и одним столбцом в режиме xs

<div class="row">
<div ng-repeat="oneExt in configAddr.ext">
    <div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
        {$index+1}}. 
        <input type="text" name="macAdr{{$index+1}}" 
       id="macAddress" ng-model="oneExt.newValue" value=""/>
    </div>
</div>

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.