Angular ng-repeat добавляет строку начальной загрузки каждые 3 или 4 столбца

111

Я ищу правильный шаблон для вставки класса строки начальной загрузки каждые 3 столбца. Мне это нужно, потому что cols не имеет фиксированной высоты (и я не хочу ее исправлять), поэтому это нарушает мой дизайн!

Вот мой код:

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

Но в каждой строке отображается только один продукт. В качестве конечного результата я хочу:

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

Могу ли я добиться этого с помощью только шаблона ng-repeat (без директивы или контроллера)? В документации представлены ng-repeat-start и ng-repeat-end, но я не могу понять, как это использовать, именно в этом случае использования! Я чувствую, что это то, что мы часто используем при создании шаблонов начальной загрузки! ? Спасибо

объятия
источник
Я думаю, вы должны моделировать свои данные таким образом, чтобы соответствовать вашему дизайну, вероятно, это должен быть многомерный массив или объект с представлением строк и столбцов, тогда вы должны перебирать строки и использовать директиву условного класса «ng-class» и внутри строки затем вы должны перебирать столбцы.
antanas_sepikas
Интересное и, безусловно, рабочее решение, но в тот день, когда я хочу отображать 4 продукта в строке вместо 3, у меня есть, чтобы изменить свою структуру данных, я бы предпочел, чтобы это оставалось в «области» чистой функциональности отображения ...
hugsbrugs
Я вижу, тогда вам, вероятно, следует выполнять итерацию по частям, как в ответе Ариэля, также вы можете найти этот пост stackoverflow.com/questions/18564888/… полезным.
antanas_sepikas
Думаю, это именно то, что вы ищете: stackoverflow.com/a/30426750/1943442
user1943442
связанный stackoverflow.com/a/25838091/759452
Адриан Бе

Ответы:

164

Ответ, получивший наибольшее количество голосов, хотя и эффективен, это не то, что я считаю подходом angular, и при этом он не использует собственные классы начальной загрузки, которые предназначены для решения этой ситуации. Как упоминалось в @claies, .clearfixкласс предназначен для таких ситуаций. На мой взгляд, самая чистая реализация выглядит следующим образом:

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

Эта структура позволяет избежать беспорядочной индексации массива продуктов, позволяет использовать чистую точечную нотацию и использовать класс clearfix по прямому назначению.

Дункан
источник
6
Это хорошая идея, однако, если вы заинтересованы в использовании flexbox, вы должны использовать это в строке, а не в div внутри строк, чтобы позволить каждому box / div иметь одинаковую высоту. Clearfix хорош, но не помогает поддерживать все в порядке.
Coded Container
3
Это мой предпочтительный ответ. Очень чисто и просто.
Hinrich
1
Отлично работает и для моей реализации! :)
Уилл Штрол
1
Отлично ... Этот ответ следует признать принятым!
Velu
3
Это идеальный ответ
Deepu
148

Я знаю, что уже немного поздно, но все же может кому-то помочь. У меня так получилось:

<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

альпар
источник
3
это действительно мне очень помогло !! Спасибо.
SupimpaAllTheWay
Это действительно легко реализовать! Спасибо!
Manas Bajaj,
Мне очень помогли. Спасибо!
Ахмад Аджми
17
Отличное решение, однако оно не проверяет, выходят ли $ index + 1 и $ index +2 за границы массива. Последние два дивизиона требуют ng-if="$index+1 < products.length"иng-if="$index+2 < products.length"
Мустафа Озтюрк
Можем ли мы сделать это для пары ключ-значение. получить следующий ключ? Вместо получения $ index + 1
bpbhat77
25

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

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

Обратите внимание, что % 6часть должна равняться количеству результирующих столбцов. Итак, если в элементе столбца у вас есть класс, col-lg-2будет 6 столбцов, поэтому используйте ... % 6.

Этот метод (за исключением ng-if) фактически задокументирован здесь: Bootstrap docs


источник
1
На мой взгляд, это лучшее решение.
user216661
Если вы новичок в начальной загрузке, легко упустить из виду тот факт, что определение строк не требуется. Это сработало отлично и представляет собой чуть более полную версию решения Дункана.
phosplait
Это именно то, что я искал.
Hinrich
@phosplait В чем преимущество этого перед Дунканом?
Jeppe
17

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

Вы правы, таблицы Bootstrap ведут себя странно, когда у вас есть столбцы с нестандартной высотой. Однако существует класс начальной загрузки, созданный для борьбы с этой проблемой и выполнения адаптивного сброса .

просто создайте пустое <div class="clearfix"></div>поле перед началом каждой новой строки, чтобы позволить поплавкам сбрасываться, а столбцы возвращаться в свои правильные положения.

вот бутплей .

Претензии
источник
Это не устраняет отрицательные 15 пикселей поля, которые каждый .row имеет для начальной загрузки.
Logus
Можно ли flexсделать столбцы одинаковой высоты?
Алиссон,
16

Спасибо за ваши предложения, вы меня на правильном пути!

Давайте рассмотрим полное объяснение:

  • По умолчанию HTTP-запрос AngularJS возвращает объект

  • Итак, если вы хотите использовать функцию @Ariel Array.prototype.chunk, вам нужно сначала преобразовать объект в массив.

  • А затем использовать функцию chunk В ВАШЕМ КОНТРОЛЛЕРЕ, иначе, если она используется непосредственно в ng-repeat, это приведет вас к ошибке infdig . Итоговый контроллер выглядит:

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

И HTML становится:

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

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

С другой стороны, я решил напрямую вернуть массив [] вместо объекта {} из моего файла JSON. Таким образом, контроллер становится (обратите внимание на специальный синтаксис 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 останется таким же, как указано выше.

ОПТИМИЗАЦИЯ

Последний вопрос в ожидании: как сделать его 100% AngularJS без расширения массива javascript с помощью функции chunk ... если некоторые люди хотят показать нам, подходят ли ng-repeat-start и ng-repeat-end .. . Мне любопытно ;)

РЕШЕНИЕ АНДРЕЯ

Благодаря @Andrew мы теперь знаем, что добавление класса очистки начальной загрузки каждые три (или любое другое число) элементов исправляет проблему отображения из-за разной высоты блока.

Итак, HTML становится:

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

И ваш контроллер остается довольно мягким с удаленной функцией chunck :

// 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;
        });
объятия
источник
7

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

чтобы создать массив для отображения, используйте эту функцию, например, 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)];
        })
    );
}

а затем сделайте что-то подобное, используя 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>
Ариэль Хенрисон
источник
7

На основе решения Alpar с использованием только шаблонов с anidated ng-repeat. Работает как с полными, так и с частично пустыми строками:

<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

Хуанги Йордан
источник
5

Я только что решил, что работает только в шаблоне. Решение

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

Point использует данные дважды, один - для внешнего цикла. Дополнительные теги диапазона останутся, но это зависит от того, как вы торгуете.

Если это трехколоночный макет, он будет похож на

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

Честно хотел

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

Хотя не вышло.

Ватару
источник
Я пробовал это решение, чтобы реализовать по 2 элемента в каждой строке. Например, у меня есть 5 элементов в списке, поэтому у меня должен быть вывод: 3 строки с 2 элементами / столбцами в первых 2 строках и 1 столбцом в последней строке. Проблема в том, что я получаю здесь 5 строк, а последние 2 строки пусты. Хотите знать, как это исправить? Спасибо
Maverick Riz
@MaverickAzy, спасибо за попытку. Я знаю, что существует проблема, заключающаяся в том, что если высота этих элементов отличается, это не работает.
Ватару 06
Высота элементов практически одинакова. Проблема в том, что я должен получить только 3 строки, но получить 5 строк с двумя последними пустыми строками. Вы можете мне сказать, если products.length равно 5, то 5/2 + 1 =? Эта логика мне не понятна в строке № 2 для строки класса.
Maverick Riz
@MaverickAzy эти пустые строки должны быть созданы как есть. Это портит ваш макет?
Ватару 06
нет, это не портит макет. Единственное беспокойство вызывает пустые строки. Очень признателен, если вы можете мне с этим помочь. Спасибо
Maverick Riz
5

Еще одно небольшое улучшение ответа @Duncan и других ответов на основе элемента clearfix. Если вы хотите сделать контент интерактивным, вам понадобится z-index> 0, иначе clearfix будет перекрывать контент и обрабатывать щелчок.

Этот пример не работает (вы не видите указатель курсора, и щелчок ничего не сделает):

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

Пока это фиксированный :

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

Я добавил, z-index: 1чтобы контент увеличивался по сравнению с clearfix, и я удалил контейнер div, используя вместо него ng-repeat-startи ng-repeat-end(доступно в AngularJS 1.2), потому что он не работал z-index.

Надеюсь это поможет!

Обновить

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

Макгиоген
источник
Работает ли это с flexстроками, чтобы столбцы были одинаковой высоты?
Алиссон,
Я не уверен, что понимаю ваш вопрос. Это быстрый инструмент, показывающий, что делает этот код: plnkr.co/edit/4w5wZj?p=preview . На словах clearfix правильно выравнивает вторую строку заголовков: все они начинаются с одной и той же точки, но у них по-прежнему разная высота (как вы можете видеть благодаря цвету фона). Попробуйте удалить класс clearfix, чтобы узнать, каково поведение по умолчанию. Я использовал flexbox только один или два раза, но у него много свойств css, и я уверен, что вы сможете найти то, что ищете.
McGiogen
bootstrap предоставляет пример того, как сделать все столбцы в одной строке, чтобы получить одинаковую высоту самого высокого столбца. Я должен был это использовать. Проблема в том, что он теряет возможность переноса на новую строку, когда имеется более 12 столбцов, поэтому вам нужно вручную создавать новые строки. Изучив немного больше, я мог бы получить решение и опубликовать его здесь в качестве ответа, хотя я не знаю, лучший ли он. В любом случае, спасибо, ваш ответ мне помог!
Алиссон
Я впервые вижу этот пример, он действительно полезен! Рад помочь вам.
McGiogen
4

я решил это с помощью 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>
Иосип
источник
2

Лучший способ применить класс - использовать ng-class. Его можно использовать для применения классов на основе некоторого условия.

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

а затем в вашем контроллере

$scope.getRowClass = function(index){
    if(index%3 == 0){
     return "row";
    }
}
Раджив
источник
2

После объединения многих ответов и предложений здесь это мой окончательный ответ, который хорошо работает flex, что позволяет нам создавать столбцы с одинаковой высотой, он также проверяет последний индекс, и вам не нужно повторять внутренний HTML. Не использует 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>

Результат будет примерно таким:

<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
источник
1

Небольшая модификация в решении @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

Джейм
источник
0

Это сработало для меня, без сращивания или чего-то еще:

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;
};
Робби Марстон
источник
0

Born Solutions - лучший вариант, просто нужно немного подправить, чтобы удовлетворить потребности, у меня были разные адаптивные решения, и я немного изменился

<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>
Кико Сейджо
источник
0

Основываясь на ответе Alpar, вот более общий способ разбить один список элементов на несколько контейнеров (строки, столбцы, корзины и т. Д.):

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

для списка из 10 элементов генерирует:

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

Количество контейнеров можно быстро закодировать в функцию контроллера:

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

Такой подход позволяет избежать дублирования разметки элемента ( <span>{{item.name}}</span>в данном случае) в исходном шаблоне - не очень большая победа для простого диапазона, но для более сложной структуры DOM (которая у меня была) это помогает сохранить шаблон DRY.

Марк Читти
источник
0

Обновление 2019 - Bootstrap 4

Поскольку в Bootstrap 3 используются числа с плавающей запятой, для предотвращения неравномерного переноса столбцов требуется, чтобы clearfix сбрасывал каждые n (3 или 4) columns ( .col-*) .row.

Теперь, когда Bootstrap 4 использует flexbox , больше нет необходимости переносить столбцы в отдельные .rowтеги или вставлять дополнительные div, чтобы заставить cols переноситься каждые n столбцов.

Вы можете просто повторить все столбцы в одном .rowконтейнере.

Например, 3 столбца в каждой визуальной строке:

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

Итак, для Bootstrap ng-repeat просто:

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

Демо: https://www.codeply.com/go/Z3IjLRsJXX

Зим
источник
0

Я сделал это только с помощью boostrap, вы должны быть очень осторожны в расположении строки и столбца, вот мой пример.

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

Алексис Суарес
источник