AngularJS:遍历嵌套数组

时间:2014-04-26 19:42:41

标签: angularjs angularjs-ng-repeat

我正在使用具有结构的嵌套数组...

$scope.items = [{attr1: val1, 
  attr2: val2,
  items: [{
     attr1: val1, 
     attr2: val2,
     items: [{
     ... 
     }, ...]
  }, ...]
}, ...];

进入ng-repeatng-include就像这样

<div ng-repeat="item in items" ng-include="'/path/to/template.tpl.html'"></div>

template.tpl.html

<div>{{item.attr1}}<\div>
<div>{{item.attr2}}<\div>
<div ng-click="fnAddNewItemBelow(item, $parent)"><\div>
<div ng-repeat="item in item.items" ng-include="'/path/to/template.tpl.html'"><\div>

现在,在控制器中,我通常想做像

这样的事情
  • 找到项目的父级
  • 找到一个项目的兄弟姐妹
  • 计算兄弟姐妹的数量
  • 找出项目嵌套的深度级别
  • 在嵌套的任何级别插入或删除项目

但我不确定如何优雅地做到这一点。例如,我想要实现fnAddNewItemBelow。我能解决的两个选择是

遍历范围

使用Angular提供的嵌套范围结构

// pseudo-code only
$scope.fnAddNewItemBelow = function (item, parent) {
  var newItem = ...;

  // add newItem as a sibling after the item that was ng-clicked
  // parent.$parent is necessary because the ng-include adds another scope layer (I think)
  parent.$parent.item.items.push(newItem);

  // (probably need to use .splice in case there are items after item, 
  //  but I'm keeping it simple)
}

但这很丑陋,因为它假设过多的结构(如果我将ng-if放到<div ng-click...上,这会增加另一个范围级别......那么我需要parent.$parent.$parent.item.items.push(newItem) )。

递归迭代嵌套数组,直到找到item.id

另一种方法是直接在$scope.items上操作,因为Angular会更新与之关联的UI和范围。我可以使用for循环通过$scope.items递归迭代,并在通过它具有的某个唯一ID定位item之后,在它之后插入newItem

// pseudo-code only
$scope.fnAddNewItemBelow = function (item) {
  var newItem = ...;

  // add newItem as a sibling after the item that was ng-clicked
  fnSomeFunctionToFindItemAndInsertItemAfterIt(item.id, newItem);
}

fnSomeFunctionToFindItemAndInsertItemAfterIt (itemId, newItem) {
  // fancy recursive function that for loops through each item, and calls 
  // itself when there are children items. When it finds item with itemId, it 
  // splices in the newItem after
}

我不喜欢这个,因为每次我想用嵌套数组做某事时都需要遍历整个项目树。

有更优雅的解决方案吗?

2 个答案:

答案 0 :(得分:7)

如果在item.items表达式中对ng-repeat进行别名,则angular将跟踪数组结构和层次关系。

<div ng-repeat="item in items = item.items">

然后,对树的操作可以简单地传入item$indexitems数组 - 而不知道完整的数组结构:

  <button ng-click="addItem(item)">Add to my items</button>
  <button ng-click="addSiblingItem(items, $index)">Add a sibling item</button>
  <button ng-click="deleteMe(items, $index)">Delete Me</button>

JS:

$scope.addItem = function(item) {
  item.items.push({
    attr1: 'my new - attr1',
    attr2: 'my new - attr2',
    items: []
  });
}
$scope.addSiblingItem = function(items, position) {
  items.splice(position + 1, 0, {
    attr1: 'sibling - new attr1',
    attr2: 'sibling - new attr2',
    items: []
  });
}
$scope.deleteMe = function(items, position) {
  items.splice(position, 1);
}

要获得兄弟姐妹的数量,您可以参考items.length

<h3>Item #{{$index + 1}} of {{items.length}}</h3>

如果您确实需要从子项访问父级兄弟姐妹,可以为parent = item添加其他别名,并使用ng-init将其添加到项目中:

ng-repeat="item in items = (parent = item).items" ng-init="item.parent = parent"

然后您可以访问祖父母(parent.parent)及其items(父母兄弟姐妹)。

此外,您可以使用ng-init跟踪当前的嵌套级别:

ng-init="item.parent = parent; item.level = parent.level + 1"

这是一个有效的演示:http://plnkr.co/xKSwHAUdXcGZcwHTDmiv

答案 1 :(得分:2)

在渲染数据之前,您可以做一些准备工作。一个递归运行数据以设置级别值和每个项目的父级链接。 使用LoDash的数据示例:

var level = 0;
_.each($scope.items, function(item){recursive(item, level)});

function recursive(item, level){
    item.level = level;
    _.each(item.items, function(innerItem){
        innerItem.parent = item;
        recursive(innerItem, level+1);
    });
}

所以现在你可以轻松获得每个项目的父母和兄弟姐妹。

找到项目的父级 - &gt; item.parent

找到项目的兄弟姐妹 - &gt; item.parent.items[i]

计算兄弟姐妹的数量 - &gt; item.parent.items.length

找出项目嵌套深度的级别 - &gt; item.level

在嵌套的任何级别插入或删除项目(移动操作示例) - &gt;

newParent.items.push(item);
_.remove(item.parent.items, function(child){return child == item;});

我遇到的这种方法的唯一减号 - 你不能轻易地克隆整个树而不进行无休止的递归。但是你可以制作不会复制链接的自定义克隆功能。