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

Posted

技术标签:

【中文标题】如何在没有 html 元素的情况下使用 ng-repeat【英文标题】:How to use ng-repeat without an html element 【发布时间】:2012-07-14 12:19:05 【问题描述】:

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

复杂之处在于数组的每个元素都将转换为表格的一、二或三行。

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

例如,如果我在 <span> 上使用 ng-repeat,我会得到:

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

【问题讨论】:

也许你应该使用“replace: true”?看到这个:***.com/questions/11426114/… 另外,为什么不能在 tr 本身上使用 ng-repeat? @Tommy,因为“数组的每个元素都将转换为表格的一、二或三行”。据我所知,如果我在 tr 上使用 ng-repeat,我会得到每个数组元素一行。 好的,我明白了。在中继器中使用之前,您不能先将模型展平吗? @Tommy,不。一个数组元素生成的1-3个trs结构不同。 【参考方案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>

我认为这是有效的:)

【讨论】:

虽然这有效,但它仅在数组包含三个元素时才有效。 只要确保数组有 3 个元素,即使它们是空数组(使用空数组的 ng-repeat 根本不渲染任何东西)。 我的意思是 OP 可能想要一个适用于数组中可变数量项目的解决方案。我认为将“这个数组中必须有三个项目”硬编码到模板中将是一个糟糕的解决方案。 在对他的问题的最后评论中,他说元素不会具有相同的结构,因此对每个结构进行硬编码是不可避免的。【参考方案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>

【讨论】:

不,不是。绝对不建议在 ngRepeat 表达式中调用函数 在我的例子中,我确实需要为索引 != 0 和 ng-repeat-startng-repeat-end 破坏我的设计的每个元素添加一个分隔符,因此解决方案是创建一个额外的变量添加这个分隔符对象在每个元素之前并迭代新的变量:&lt;div class="c477_group"&gt; &lt;div class="c477_group_item" ng-repeat="item in itemsWithSeparator" ng-switch="item.id" ng-class="'-divider' : item.id == 'SEPARATOR'"&gt; &lt;div class="c478" ng-switch-when="FAS"/&gt; &lt;div class="c478" ng-switch-when="SEPARATOR" /&gt; &lt;div class="c479" ng-switch-default /&gt; &lt;/div&gt; &lt;/div&gt;【参考方案3】:

更新:如果您使用的是 Angular 1.2+,请使用 ng-repeat-start。请参阅@jmagnusson 的回答。

否则,将 ng-repeat 放在 tbody 上怎么样? (AFAIK,在一个表中有多个

是可以的。)
<tbody ng-repeat="row in array">
  <tr ng-repeat="item in row">
     <td>item</td>
  </tr>
</tbody>

【讨论】:

虽然这在技术上可能可行,但非常令人失望的是,这个常见用例的答案是您必须注入任意(否则不必要的)标记。我有同样的问题(重复的行组——一个标题 TR 和一个或多个子 TR,作为一组重复)。与其他模板引擎无关紧要,在 Angular 看来充其量是 hacky。 同意。我正在尝试对 DT+DD 元素进行重复。如果不添加无效的包装元素,就无法做到这一点 @DavidLin,在 Angular v1.2(无论何时出现)中,您将能够重复多个元素,例如 dt 和 dd:youtube.com/watch?v=W13qDdJDHp8&t=17m28s 这在 KnockoutJS 中使用无容器控制流语法优雅地处理:knockoutjs.com/documentation/foreach-binding.html。希望很快能看到 Angular 做类似的事情。 此答案已过时,请改用 ng-repeat-start 【参考方案4】:

从 AngularJS 1.2 开始,有一个名为 ng-repeat-start 的指令可以满足您的要求。有关如何使用它的说明,请参阅我的回答 in this question。

【讨论】:

Angular 已经迎头赶上,现在这是正确的解决方案。【参考方案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" >
      <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-startng-repeat-end 的标签,设置ng-if="0",不要插入到页面中。这样内部的内容就会完全按照knockoutjs中的方式处理(使用&lt;!--...--&gt;中的命令),不会有垃圾。

【讨论】:

在设置 ng-if="0" 后,会抛出错误,提示找不到匹配的 ng-repeat-end。【参考方案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>

注意隐藏开始和结束 div 的 ng-if="false"。

应该打印出来

name1,name1_1,name1_2,name2,name2_1,name2_2,

【讨论】:

【参考方案7】:

我只想发表评论,但我的声誉仍然不足。所以我正在添加另一个解决问题的解决方案。我真的很想反驳@bmoeskau 的说法,即解决这个问题需要一个“最好的hacky”解决方案,并且由于最近在讨论中出现了这个问题,即使这篇文章已经有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']

这适用于任何具有子项的树状结构。如果你想要整个项目而不是一个属性,你可以进一步缩短展平功能。

希望有帮助。

【讨论】:

不错的一个。虽然它没有直接帮助我,但它确实让我想到了另一种解决方案。非常感谢!【参考方案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 将自身绑定到 &lt;remove&gt; 元素并按其应有的方式循环,并且因为我们使用了 ng-repeat-start / ng-repeat-end 它循环一个 html 块而不仅仅是一个元素。

然后自定义删除指令将&lt;remove&gt; 开始和结束元素与&lt;!--removed element--&gt; 一起放置

【讨论】:

以上是关于如何在没有 html 元素的情况下使用 ng-repeat的主要内容,如果未能解决你的问题,请参考以下文章

如何在没有外部 css 文件的情况下将 div 元素居中

如何在没有元素重叠的情况下在 java 中使用布局?

如何使用 BigQuery 在没有每个元素的情况下获取所有总和值?

如何在没有内存泄漏的情况下删除 DOM 元素?

JavaScript:如何在没有 innerHTML 的情况下替换元素内的代码

如何在html已有背景的情况下,在上面添加一个盒子?