如何在没有 html 元素的情况下使用 ng-repeat

How to use ng-repeat without an html element

提问人:fadedbee 提问时间:7/15/2012 最后编辑:fadedbee 更新时间:7/21/2019 访问量:104315

问:

我需要使用(在 AngularJS 中)列出数组中的所有元素。ng-repeat

复杂的是,数组的每个元素都将转换为表的一行、两行或三行。

如果用于元素,我无法创建有效的 html,因为 和 之间不允许使用重复元素。ng-repeat<tbody><tr>

例如,如果我使用 ng-repeat on ,我会得到:<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>          
angularjs 的

评论

0赞 7/15/2012
也许你应该使用“replace: true”?看到这个:stackoverflow.com/questions/11426114/...
0赞 7/15/2012
另外,为什么不能在 tr 本身上使用 ng-repeat?
2赞 fadedbee 7/15/2012
@Tommy,因为“数组的每个元素都将转换为表的一行、两行或三行”。据我了解,如果我在 上使用 ng-repeat,我将得到每个数组元素的一行。tr
1赞 7/15/2012
好的,我明白了。你不能在中继器中使用模型之前先把它展平吗?
0赞 fadedbee 7/15/2012
@Tommy,没有。由一个数组元素生成的 1-3 个 s 不具有相同的结构。tr

答:

-2赞 Renan Tomal Fernandes 7/16/2012 #1
<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>

我认为这是有效的:)

评论

0赞 btford 7/16/2012
虽然这有效,但只有当数组有三个元素时,它才会起作用。
0赞 Renan Tomal Fernandes 7/16/2012
只要确保数组有 3 个元素,即使它们是空数组(带有空数组的 ng-repeat 根本不会渲染任何东西)。
7赞 btford 7/16/2012
我的观点是,OP 可能想要一个适用于数组中可变数量的项目的解决方案。我认为将“此数组中必须有三个项目”硬编码到模板中将是一个糟糕的解决方案。
0赞 Renan Tomal Fernandes 7/16/2012
在关于他的问题的最后评论中,他说元素不会具有相同的结构,因此对每个结构进行硬编码是不可避免的。
20赞 btford 7/16/2012 #2

您可能希望在控制器中展平数据:

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>

评论

1赞 Nik 8/4/2015
不,不是。绝对不建议在 ngRepeat 表达式中调用函数
0赞 hermeslm 1/7/2020
就我而言,我确实需要为索引 != 0 和 的每个元素添加一个分隔符,从而破坏了我的设计,因此解决方案是创建一个额外的变量,在每个元素之前添加此分隔符对象并迭代新的变量:ng-repeat-startng-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>
68赞 Mark Rajcok 8/17/2012 #3

更新:如果您使用的是 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>

评论

64赞 Brian Moeskau 6/17/2013
虽然这在技术上可能有效,但非常令人失望的是,这个常见用例的答案是你必须注入任意(否则不必要的)标记。我有同样的问题(重复的行组 - 一个标题 TR 与一个或多个子 TR,作为一个组重复)。对于其他模板引擎来说微不足道,对于 Angular 来说似乎充其量是 hacky。
1赞 David Lin 7/18/2013
同意。我正在尝试对 DT+DD 元素进行重复。如果不添加无效的包装元素,就无法做到这一点
2赞 Mark Rajcok 7/18/2013
@DavidLin,在 Angular v1.2 中(无论何时发布),您将能够在多个元素上重复,例如 dt 和 dd: youtube.com/watch?v=W13qDdJDHp8&t=17m28s
0赞 Cory House 4/29/2014
这在 KnockoutJS 中使用无容器控制流语法优雅地处理:knockoutjs.com/documentation/foreach-binding.html。希望很快看到 Angular 做类似的事情。
3赞 iwein 6/24/2014
这个答案已经过时了,请改用ng-repeat-start
71赞 jmagnusson 9/17/2013 #4

从 AngularJS 1.2 开始,有一个名为 Directive 的指令,它完全可以满足您的要求。有关如何使用它的描述,请参阅我在此问题中的回答。ng-repeat-start

评论

0赞 iwein 6/24/2014
Angular 已经迎头赶上,这是现在正确的解决方案。
40赞 Duka Árpád 7/10/2015 #5

如果您使用 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>

重要的一点是:对于用于和设置的标签,不要让插入到页面中。这样,内部内容将完全按照 knockoutjs 中的处理方式进行处理(使用 中的命令),并且不会出现垃圾。ng-repeat-startng-repeat-endng-if="0"<!--...-->

评论

0赞 vikky MCTS 3/30/2017
设置 ng-if=“0” 后,它会抛出错误,说找不到匹配的 ng-repeat-end。
10赞 agelbess 8/14/2015 #6

以上是正确的,但对于更笼统的答案来说,这还不够。我需要嵌套 ng-repeat,但要保持在相同的 html 级别,这意味着将元素写在同一个父级中。 tags 数组包含的 tag(s) 也具有 tags 数组。 它实际上是一棵树。

[{ 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。

它应该打印

名称1,name1_1,name1_2,名称2,name2_1,name2_2,

1赞 Ar Es 9/30/2015 #7

我只想发表评论,但我的声誉仍然缺乏。所以我正在添加另一个解决方案,也可以解决问题。我真的很想反驳 @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赞 Marian Zburlea 12/31/2015
好东西。虽然它没有直接帮助我,但它确实让我想到了另一种解决方案。多谢!
0赞 Clint 7/21/2019 #8

寻求真正有效的解决方案

[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 将自身绑定到元素并按应有的方式循环,并且由于我们使用了 ng-repeat-start / ng-repeat-end,因此它循环了一个 html 块,而不仅仅是一个元素。<remove>

然后,自定义 remove 指令将 start 和 finish 元素与<remove><!--removed element-->