问题
I am working with a nested array with the structure...
$scope.items = [{attr1: val1,
attr2: val2,
items: [{
attr1: val1,
attr2: val2,
items: [{
...
}, ...]
}, ...]
}, ...];
which goes into an ng-repeat with ng-include like this
<div ng-repeat="item in items" ng-include="'/path/to/template.tpl.html'"></div>
and template.tpl.html is
<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>
Now, in the controller, I commonly want to do things like
- find an item's parent
- find an item's sibling
- make counts of siblings
- find out how many levels deep an item is nested
- insert or delete items at any level of the nest
But I'm not sure how to do this elegantly. Eg imagine I wanted to implement fnAddNewItemBelow. The two options I can work out are
Traverse scopes
Use the nested scopes structure that Angular provides
// 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)
}
But this is ugly because it assumes too much about the structure (what if I put an ng-if onto the <div ng-click..., which added another scope level... then I'd need parent.$parent.$parent.item.items.push(newItem)).
Iterate nested array recursively until item.id is found
The alternative is to operate directly on $scope.items, since Angular will update UI and scopes associated with it. I can iterate recursively through $scope.items using for loops and after locating item by some unique id that it has, insert newItem after it
// 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
}
I don't like this because it requires iterating through the entire items tree every time I want to do something with the nested array.
Are there more elegant solutions?
回答1:
If you alias item.items in the ng-repeat expression, angular will keep track of the array structure and hierarchical relationships for you.
<div ng-repeat="item in items = item.items">
Then, operations on the tree can simply pass in the item, the $index, or the array of items - without knowledge of the full array structure:
<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);
}
To get the number of siblings, you can refer to items.length:
<h3>Item #{{$index + 1}} of {{items.length}}</h3>
If you really need to access the parent siblings from child items, you can add another alias for parent = item and add it to the item using ng-init:
ng-repeat="item in items = (parent = item).items" ng-init="item.parent = parent"
Then you have access to the grandparent (parent.parent) and its items (the parent siblings).
In addition, you can keep track of the current nest level using ng-init:
ng-init="item.parent = parent; item.level = parent.level + 1"
Here is a working demo: http://plnkr.co/xKSwHAUdXcGZcwHTDmiv
回答2:
Before rendering data, you can make some preparations. One recursive run over your data to set level value and a link to the parent to each item. Example with your data using 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);
});
}
So now you can easily get parent and siblings of each item.
find an item's parent -> item.parent
find an item's sibling -> item.parent.items[i]
make counts of siblings -> item.parent.items.length
find out how many levels deep an item is nested -> item.level
insert or delete items at any level of the nest (move operation example) ->
newParent.items.push(item);
_.remove(item.parent.items, function(child){return child == item;});
The only minus of this approach which i met - you can not easily clone whole tree without going into endless recursion. But you can make custom cloning function which will not copy links.
来源:https://stackoverflow.com/questions/23315679/angularjs-traversing-nested-arrays