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
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
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)
).
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?
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With