使用ng-repeat和嵌套循环添加行

43

我正在寻找一种方法来向表格中添加行。我的数据结构看起来像这样:

  rows = [
    { name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
    { name : 'row2' }
  ];

我想创建一个看起来像这样的表格:

  table
     row1
     row1.1
     row1.2
     row2

angular js的ng-repeat可以实现这个吗?如果不行,有没有其他“angular”方式可以做到这一点?

编辑:扁平化数组将是一个糟糕的解决方案,因为如果我可以迭代子元素,我可以在单元格内使用不同的html标签、其他css类等。


首先将数据结构转换为平面数组,然后使用该新数组构建表格作为普通循环(类似于当前答案,不使用子循环)。 - Ian
例如,这个(http://jsfiddle.net/t6RLz/)将数组转换为一个新的包含4个项目的数组(原始的2个项目和2个子行,顺序正确)。然后,就像下面的答案一样循环(因为我对angularjs一无所知),但不要使用内部循环。我相信jsFiddle中的代码可以被改进和/或缩短,但它更多的只是为了给你一个想法。 - Ian
1
然后为子行对象添加一个新属性,以指示它们是子行。然后在 HTML 中循环时,检查该特定属性是否存在,并根据其执行操作。 - Ian
6个回答

75

经过一年多的时间,我们找到了一个解决方法,至少可以适用于两个级别(父级->子级)。
只需重复tbody即可:

<table>
  <tbody ng-repeat="row in rows">
    <tr>
      <th>{{row.name}}</th>
    </tr>
    <tr ng-repeat="sub in row.subrows">
      <td>{{sub.name}}</td>
    </tr>
  </tbody>
</table>

据我所知,所有的浏览器都支持在表格内使用多个tbody元素。


2
显然这是有效的HTML,甚至允许为每组行设置特殊样式。 - z0r
5
如果您使用Bootstrap的table-striped功能,可能无法按照您想要的方式显示。 - Josh Petitt
@josh 应该是将 CSS 调整到语义化 HTML 上,而不是反过来。但在这里,Angular 和 Bootstrap 一样擅长滥用 DOM... - Olivvv
4
很棒,它节省了我很多不必要的代码。 - Anatoly
太好了!我不明白为什么这不能适用于孙子孙女以及儿童等等。 - Alexander Mills
显示剩余2条评论

28

三年多过去了,我面临着同样的问题,在写下指令之前,我尝试了这个方法,对我来说效果非常好:

<table>
    <tbody>
        <tr ng-repeat-start="row in rows">
            <td>
                {{ row.name }}
            </td>
        </tr>
        <tr ng-repeat-end ng-repeat="subrow in row.subrows">
            <td>
                {{ subrow.name }}
            </td>
        </tr>
    </tbody>
</table>

这确实应该是被接受的答案。是否可能在ng-repeat-start内嵌另一个ng-repeat-start - Thorkil Holm-Jacobsen
我对此毫不知情!使用嵌套的ng-repeat-start指令,我能够在表格层次结构中深入 3 层。这绝对应该是答案。 - brettvd
2
是的@ThorkilHolm-Jacobsen。它非常好地解决了我的层次结构表问题。 提示:如果您需要多个嵌套在一起,请利用隐藏的tr来包含ng-repeat-end - brettvd
最简单但杀手级的解决方案!此外,您还可以访问父row对象的属性。 - Farhan Ghumra
确实可以有几个嵌套的 ng-repeat-start,只需在 AngularJS 中有相应数量的 ng-repeat-end 即可避免出现错误。 - Edgar Quintero

23

使用ng-repeat无法实现此操作。但是,您可以使用指令来实现。

<my-table rows='rows'></my-table>

演示代码.

myApp.directive('myTable', function () {
    return {
        restrict: 'E',
        link: function (scope, element, attrs) {
            var html = '<table>';
            angular.forEach(scope[attrs.rows], function (row, index) {
                html += '<tr><td>' + row.name + '</td></tr>';
                if ('subrows' in row) {
                    angular.forEach(row.subrows, function (subrow, index) {
                        html += '<tr><td>' + subrow.name + '</td></tr>';
                    });
                }
            });
            html += '</table>';
            element.replaceWith(html)
        }
    }
});

这是一个非常好的答案。我认为更加详细的做法是将行的扩展打包成一个指令,然后添加到tr元素中。 - schlingel
@schlingel,这似乎是不可能的:fiddle。我认为我们不能使用链接函数将另一个<tr>...</tr>元素放入现有元素中(即正在使用ng-repeat的元素)。 - Mark Rajcok
你是对的,在这种情况下,after()似乎不能按预期工作。 - schlingel
很棒的答案...似乎再次强调了Angular不能做所有事情!谢谢@MarkRajcok - AdityaSaxena
很棒的答案,我做了一点小改进,这样你就可以拥有任意多的级别,而不仅仅是2个:http://jsfiddle.net/vGUsu/108/。我自己的项目需要这个 :) - Erik van de Ven

15

我有点惊讶于这么多人支持使用自定义指令和创建由$watch更新的代理变量。

正是出于这样的问题,AngularJS过滤器才应运而生!

文档中说:

A filter formats the value of an expression for display to the user.

我们并不打算操纵数据,只是为了以一种不同的方式显示它而进行格式化。因此,让我们创建一个过滤器,该过滤器接收我们的行数组,将其展平,并返回展平后的行。
.filter('flattenRows', function(){
return function(rows) {
    var flatten = [];
    angular.forEach(rows, function(row){
      subrows = row.subrows;
      flatten.push(row);
      if(subrows){
        angular.forEach(subrows, function(subrow){
          flatten.push( angular.extend(subrow, {subrow: true}) );
        });
      }
    });
    return flatten;
}
})

现在我们需要将过滤器添加到ngRepeat中:
<table class="table table-striped table-hover table-bordered">
  <thead>
    <tr>
      <th>Rows with filter</th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="row in rows | flattenRows">
          <td>{{row.name}}</td>
      </tr>
  </tbody>
</table>

如果需要,现在可以将您的表格与其他筛选器结合使用,例如搜索。

虽然多个tbody的方法很方便,也是有效的,但它会破坏任何依赖子行顺序或索引的CSS,例如“条纹”表,并假定您未对tbody进行样式设置,以便不要重复。

这里有一个plunk:http://embed.plnkr.co/otjeQv7z0rifPusneJ0F/preview

编辑:我添加了一个subrow值,并在表格中使用它来显示哪些行是子行,因为您提到了这个问题。


1
很棒的答案!我喜欢这个想法。 :) - GG.

6

是的,这是可能的:

控制器:

app.controller('AppController',
    [
      '$scope',
      function($scope) {
        $scope.rows = [
          { name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
          { name : 'row2' }
        ];

      }
    ]
  );

HTML:

<table>
  <tr ng-repeat="row in rows">
    <td>
      {{row.name}}
      <table ng-show="row.subrows">
        <tr ng-repeat="subrow in row.subrows">
          <td>{{subrow.name}}</td>
        </tr>
      </table>
    </td>
  </tr>
</table>

Plunker

如果您不想要子表格,请将行压缩(同时注释子行以便区分):

控制器:

function($scope) {
  $scope.rows = [
    { name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
    { name : 'row2' }
  ];

  $scope.flatten = [];
  var subrows;
  $scope.$watch('rows', function(rows){
    var flatten = [];
    angular.forEach(rows, function(row){
      subrows = row.subrows;
      delete row.subrows;
      flatten.push(row);
      if(subrows){
        angular.forEach(subrows, function(subrow){
          flatten.push( angular.extend(subrow, {subrow: true}) );
        });
      }
    });
    $scope.flatten = flatten;
  });

}

HTML:

<table>
  <tr ng-repeat="row in flatten">
    <td>
      {{row.name}}
    </td>
  </tr>
</table>

Plunker


1
这是基本情况。我不想要一个表格中嵌套另一个表格,我希望子行在同一张表格下面排列。 - schlingel
在这种情况下,您应该简单地展开您的“rows”对象,以便所有行定义都在同一节点下。使用ng-repeat无法完成此操作。 - Stewie

0
这是一个例子。这段代码打印出了peopeByCity数组中所有人的名字。
TS:
export class AppComponent {
  peopleByCity = [
    {
      city: 'Miami',
      people: [
        {
          name: 'John', age: 12
        }, {
          name: 'Angel', age: 22
        }
      ]
    }, {
      city: 'Sao Paulo',
      people: [
        {
          name: 'Anderson', age: 35
        }, {
          name: 'Felipe', age: 36
        }
      ]
    }
  ]
}

HTML:

<div *ngFor="let personsByCity of peopleByCity">
  <div *ngFor="let person of personsByCity.people">
    {{ person.name }}
  </div>
</div>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接