Как использовать ng-repeat без HTML-элемента

142

Мне нужно использовать ng-repeat(в AngularJS), чтобы перечислить все элементы в массиве.

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

Я не могу создать действительный HTML, если ng-repeatиспользуется для элемента, так как между <tbody>и <tr>.

Например, если бы я использовал ng-repeat <span>, я бы получил:

<table>
  <tbody>
    <span>
      <tr>...</tr>
    </span>
    <span>
      <tr>...</tr>
      <tr>...</tr>
      <tr>...</tr>
    </span>
    <span>
      <tr>...</tr>
      <tr>...</tr>
    </span>
  </tbody>
</table>          

Который является недействительным HTML.

Но то, что мне нужно создать:

<table>
  <tbody>
    <tr>...</tr>
    <tr>...</tr>
    <tr>...</tr>
    <tr>...</tr>
    <tr>...</tr>
    <tr>...</tr>
  </tbody>
</table>          

где первая строка была сгенерирована первым элементом массива, следующие три - вторым, а пятый и шестой - последним элементом массива.

Как я могу использовать ng-repeat таким образом, чтобы элемент html, с которым он связан, «исчезал» во время рендеринга?

Или есть другое решение для этого?


Пояснение: сгенерированная структура должна выглядеть следующим образом. Каждый элемент массива может генерировать от 1 до 3 строк таблицы. В идеале ответ должен поддерживать 0-n строк на элемент массива.

<table>
  <tbody>
    <!-- array element 0 -->
    <tr>
      <td>One row item</td>
    </tr>
    <!-- array element 1 -->
    <tr>
      <td>Three row item</td>
    </tr>
    <tr>
      <td>Some product details</td>
    </tr>
    <tr>
      <td>Customer ratings</td>
    </tr>
    <!-- array element 2 -->
    <tr>
      <td>Two row item</td>
    </tr>
    <tr>
      <td>Full description</td>
    </tr>
  </tbody>
</table>          
fadedbee
источник
Может быть, вы должны использовать «заменить: правда»? Смотрите это: stackoverflow.com/questions/11426114/...
Кроме того, почему вы не можете использовать ng-repeat на самом tr?
2
@Tommy, потому что «каждый элемент массива преобразуется в одну, две или три строки таблицы». trНасколько я понимаю, если бы я использовал ng-repeat, я бы получил по одной строке на элемент массива.
увядшая пчела
1
Да я вижу. Разве вы не можете просто расплющить модель, прежде чем использовать ее в репитере?
@ Томми, нет. 1-3 trс, которые генерируются одним элементом массива, не имеют одинаковую структуру.
увядшая пчела

Ответы:

66

Обновление: если вы используете Angular 1.2+, используйте ng-repeat-start . Смотрите ответ @ jmagnusson.

Иначе, как насчет размещения ng-repeat на tbody? (AFAIK, можно иметь несколько <tbody> в одной таблице.)

<tbody ng-repeat="row in array">
  <tr ng-repeat="item in row">
     <td>{{item}}</td>
  </tr>
</tbody>
Марк Райкок
источник
62
Хотя технически это может сработать, очень обидно, что ответом для этого распространенного варианта использования является то, что вам нужно добавить произвольную (иначе ненужную) разметку. У меня та же проблема (повторяющиеся группы строк - один заголовок TR с одним или несколькими дочерними TR, повторяющимися как группа). Тривиально с другими шаблонными движками, в лучшем случае с Angular.
Брайан Мескау
1
Согласовано. Я пытаюсь повторить элементы DT + DD. это невозможно сделать без добавления недопустимого элемента обтекания
Дэвид Лин
2
@DavidLin, в Angular v1.2 (когда бы он ни появлялся) вы сможете повторять несколько элементов, например, dt и dd: youtube.com/watch?v=W13qDdJDHp8&t=17m28s
Марк Раджкок
Это элегантно обрабатывается в KnockoutJS с использованием синтаксиса потока управления без контейнера: knockoutjs.com/documentation/foreach-binding.html . Надеюсь, что Angular скоро сделает что-то подобное.
Кори Дом
3
Этот ответ устарел, вместо этого используйте ng-repeat-start
iwein
73

Начиная с AngularJS 1.2, существует директива, ng-repeat-startкоторая выполняет именно то, что вы просите. Смотрите мой ответ в этом вопросе для описания того, как его использовать.

jmagnusson
источник
Angular догнал, теперь это правильное решение.
iwein
33

Если вы используете ng> 1.2, вот пример использования ng-repeat-start/endбез генерации ненужных тегов:

<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
    <script>
      angular.module('mApp', []);
    </script>
  </head>
  <body ng-app="mApp">
    <table border="1" width="100%">
      <tr ng-if="0" ng-repeat-start="elem in [{k: 'A', v: ['a1','a2']}, {k: 'B', v: ['b1']}, {k: 'C', v: ['c1','c2','c3']}]"></tr>

      <tr>
        <td rowspan="{{elem.v.length}}">{{elem.k}}</td>
        <td>{{elem.v[0]}}</td>
      </tr>
      <tr ng-repeat="v in elem.v" ng-if="!$first">
        <td>{{v}}</td>
      </tr>

      <tr ng-if="0" ng-repeat-end></tr>
    </table>
  </body>
</html>

Важный момент: для тегов, которые используются ng-repeat-startи ng-repeat-endустановлены ng-if="0", чтобы не вставлять на страницу. Таким образом, внутреннее содержимое будет обрабатываться точно так же, как в knockoutjs (с использованием команд in <!--...-->), и мусора не будет.

Дука Арпад
источник
после установки ng-if = "0" выдает ошибку, говорящую о невозможности найти соответствующий ng-repeat-end.
Викки MCTS
19

Возможно, вы захотите сгладить данные в вашем контроллере:

function MyCtrl ($scope) {
  $scope.myData = [[1,2,3], [4,5,6], [7,8,9]];
  $scope.flattened = function () {
    var flat = [];
    $scope.myData.forEach(function (item) {
      flat.concat(item);
    }
    return flat;
  }
}

А потом в HTML:

<table>
  <tbody>
    <tr ng-repeat="item in flattened()"><td>{{item}}</td></tr>
  </tbody>
</table>
btford
источник
1
Нет, это не так. Вызов функции внутри выражения ngRepeat абсолютно не рекомендуется
Ник
В моем случае, я должен добавить разделитель для каждого элемента , где индекс = 0 и! ng-repeat-start, ng-repeat-endСломать мой дизайн, поэтому решение должно было создать дополнительную переменную добавить этот объект разделитель перед каждым элементом и перебирать новый вар: <div class="c477_group"> <div class="c477_group_item" ng-repeat="item in itemsWithSeparator" ng-switch="item.id" ng-class="{'-divider' : item.id == 'SEPARATOR'}"> <div class="c478" ng-switch-when="FAS"/> <div class="c478" ng-switch-when="SEPARATOR" /> <div class="c479" ng-switch-default /> </div> </div>
hermeslm
6

Вышеприведенное верно, но для более общего ответа этого недостаточно. Мне нужно было вложить ng-repeat, но остаться на том же уровне html, то есть записать элементы в том же родительском элементе. Массив тегов содержит теги, которые также имеют массив тегов. Это на самом деле дерево.

[{ name:'name1', tags: [
  { name: 'name1_1', tags: []},
  { name: 'name1_2', tags: []}
  ]},
 { name:'name2', tags: [
  { name: 'name2_1', tags: []},
  { name: 'name2_2', tags: []}
  ]}
]

Итак, вот что я в итоге сделал.

<div ng-repeat-start="tag1 in tags" ng-if="false"></div>
    {{tag1}},
  <div ng-repeat-start="tag2 in tag1.tags" ng-if="false"></div>
    {{tag2}},
  <div ng-repeat-end ng-if="false"></div>
<div ng-repeat-end ng-if="false"></div>

Обратите внимание на ng-if = "false", который скрывает начальный и конечный div.

Это должно напечатать

name1, name1_1, name1_2, name2, NAME2_1, name2_2,

Αλέκος
источник
1

Я хотел бы просто прокомментировать, но моей репутации все еще не хватает. Поэтому я добавляю другое решение, которое также решает проблему. Мне бы очень хотелось опровергнуть заявление @bmoeskau о том, что решение этой проблемы требует решения «в лучшем случае взломано», и, поскольку об этом недавно говорилось в дискуссии, хотя этому посту уже 2 года, я хотел бы добавить свой собственные два цента:

Как указал @btford, вы, похоже, пытаетесь превратить рекурсивную структуру в список, поэтому сначала вы должны сгладить эту структуру в списке. Его решение делает это, но есть мнение, что вызов функции внутри шаблона не элегантен. если это правда (если честно, я не знаю), не потребует ли это выполнения функции в контроллере, а не директивы?

в любом случае, ваш html требует список, поэтому область его отображения должна иметь этот список для работы. Вы просто должны сплющить структуру внутри вашего контроллера. как только у вас есть массив $ scope.rows, вы можете сгенерировать таблицу с помощью одного простого ng-repeat. Без взлома, без излишеств, просто так, как это было задумано.

Директивы Angulars не лишены функциональности. Они просто заставляют вас писать действительный HTML. У моего коллеги была похожая проблема, со ссылкой на @bmoeskau в поддержку критики в отношении шаблонов / рендеринга ангуляров. Глядя на точную проблему, выяснилось, что он просто хотел сгенерировать открытый тег, затем закрывающий тег где-нибудь еще и т. Д., Как в старые добрые времена, когда мы объединяли наш html из строк ... верно? нет.

Что касается сглаживания структуры в список, вот еще одно решение:

// assume the following structure
var structure = [
    {
        name: 'item1', subitems: [
            {
                name: 'item2', subitems: [
                ],
            }
        ],
    }
];
var flattened = structure.reduce((function(prop,resultprop){
    var f = function(p,c,i,a){
        p.push(c[resultprop]);
        if (c[prop] && c[prop].length > 0 )
          p = c[prop].reduce(f,p);
        return p;
    }
    return f;
})('subitems','name'),[]);

// flattened now is a list: ['item1', 'item2']

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

надеюсь, это поможет.

Ар Эс
источник
Хороший. Хотя это не помогло мне напрямую, оно открыло мне разум для другого решения. Большое спасибо!
Мариан Збурлеа
0

для решения, которое действительно работает

HTML

<remove  ng-repeat-start="itemGroup in Groups" ></remove>
   html stuff in here including inner repeating loops if you want
<remove  ng-repeat-end></remove>

добавить директиву angular.js

//remove directive
(function(){
    var remove = function(){

        return {    
            restrict: "E",
            replace: true,
            link: function(scope, element, attrs, controller){
                element.replaceWith('<!--removed element-->');
            }
        };

    };
    var module = angular.module("app" );
    module.directive('remove', [remove]);
}());

для краткого объяснения,

ng-repeat связывается с <remove>элементом и выполняет циклы, как и должно быть, и поскольку мы использовали ng-repeat-start / ng-repeat-end, он циклически повторяет блок html, а не просто элемент.

Затем пользовательская директива удаления помещает <remove>начальный и конечный элементы<!--removed element-->

Clint
источник
-2
<table>
  <tbody>
    <tr><td>{{data[0].foo}}</td></tr>
    <tr ng-repeat="d in data[1]"><td>{{d.bar}}</td></tr>
    <tr ng-repeat="d in data[2]"><td>{{d.lol}}</td></tr>
  </tbody>
</table>

Я думаю, что это действительно так :)

Ренан Томаль Фернандес
источник
Хотя это работает, оно будет работать, только если массив состоит из трех элементов.
Btford
Просто убедитесь, что массив имеет 3 элемента, даже если они являются пустыми массивами (ng-repeat с пустым массивом просто ничего не рендерит).
Ренан Томаль Фернандес
7
Моя точка зрения заключалась в том, что OP, вероятно, хочет решение, которое работает для переменного числа элементов в массиве. Я предполагаю, что жесткое кодирование «должно быть три элемента в этом массиве» в шаблоне будет плохим решением.
Btford
В последнем комментарии к своему вопросу он говорит, что элементы не будут иметь одинаковую структуру, поэтому жесткое кодирование каждой структуры неизбежно.
Ренан Томаль Фернандес