Open arthur-white opened 9 years ago
I am currently struggling with the same issue. I'm going to write up my thought process so far here. I'm sorry in advance for the lack of clarity. I'm not used to writing tutorials, but I hope this might help someone down the line.
First, I've set up my API to return JSON data with this general structure:
{
"count" : "10",
"pages" : {
"current" : 1,
"total" : 2,
"size" : 5,
"prev" : "#",
"next" : "http://.../api/categories/all/id+category_id+title?size=5&page=2"
},
"items" : [
{"id" : 1, "category_id" : 0, "title" : "Hello" },
{"id" : 2, "category_id" : 1, "title" : "World" },
{"id" : 4, "category_id" : 1, "title" : "Foo" },
{"id" : 3, "category_id" : 1, "title" : "Bar" },
{"id" : 7, "category_id" : 1, "title" : "Soap" }
]
}
This data is for a sample query on the categories
database. I'm following PHP ActiveRecord conventions on table associations, so category_id
represents the parent of each category. I chose to designate category_id=0
to categories that reside in the root, figuring that in SQL, auto-incrementing primary keys start with 1
, so 0
is logically more appropriate than NULL
. This also makes our filtering a bit easier down the line, since JavaScript and NULL
tend to not get along all that well. Please note that I am querying the data with an ORDER BY category_id ASC
.
Based on this article by Oskar Hane, I created this filter:
app.filter('nested', function($filter) {
return function( arr, parent, $key_parent, $key_children ) {
var out = [];
for( var i in arr ) {
if(arr[i][$key_parent] == parent) {
var children = $filter('nested')( arr, arr[i].id, $key_parent, $key_children );
if(children.length) {
arr[i][$key_children] = children;
}
out.push(arr[i]);
}
}
return out;
};
});
I think it's not the most efficient filter, but it works for now. A more efficient filter would likely work with a copy of the initial array made with an array.slice()
, using a count-down while(array.length > 0)
loop, assigning categories to children of the out
array using the array.shift()
method. I think this is also where ORDER BY category_id ASC
would become important, wherein optimization is concerned, since it will assure that the parent is always added to out
before its children. I had trouble writing such a filter, so I'm settling for Oskar Hane's very capable version!
I've set up a $resource
factory. Let's say that I retrieve the categories from the server using this code:
$scope.categories = Category.get();
Now, if I want to, I can create my angular-treeview with this filter:
<div
data-angular-treeview="true"
data-tree-id="tree"
data-tree-model="categories.items | nested:0:'category_id':'items'"
data-node-id="id"
data-node-label="title"
data-node-children="items" >
</div>
However, this is very inefficient, since the filter will end up firing on every $digest
. It seems to be a far better idea to nest the categories right away like so:
Category.get({}, function(data) {
$scope.categories = $filter('nested')( data.items, 0, 'category_id', 'items' );
});
...and then we can simply get the tree working with the above code, but using data-tree-model="categories"
instead of the inline filter. This is the route I'm choosing to take in my project.
Alternatively, you can set up a $watch
to manually apply this filter, as per the conclusion in this article by Ben Nadel. Your code might look something like this:
Category.get({},function(data){
$scope.data.categories = data;
});
$scope.$watch('data.categories', function(newVal, oldVal) {
if( newVal.$resolved ) // $watch is fired once on init
$scope.nested = $filter('nested')( $scope.data.categories.items, 0, 'category_id', 'items' );
});
...and then set data-tree-model="nested"
in our directive. Unfortunately, I could not figure out how to avoid using a callback in the Category.get()
call in this scenario.
Lastly, if you are using the ngResource module to handle API calls, you might be able to filter the results using a custom action with the filter called inside transformResponse
. I haven't tried this route yet, so I cannot provide example code.
Again, I'm sorry for this mess of a comment, but I hope it helps someone who is also dealing with this issue. I'll edit it if I find a better solution.
When I pull the data from the DB, I get a flat array:
[... : {parent : , children: [, ... ] ...]
Not a recursive structure. This also works great for pagination, filtering and lazy loading.
Generally I'm highly confused by the choice to represent data as a json deeply nested structure.
Is there any way to alleviate this?