Reputation: 1772
I am struggle with one question and can not figure out why it happens.
I have a module (portfolioModule), service (Menu) and a directive (niMenu). The directive should render a menu item with its subitems.
HTML:
<div ng-app="portfolioModule">
<script id="menuItem" type="text/ng-template">
<li>
<a>{{item.name}}</a>
<ul>
<li ng-repeat="item in menu.getKids(item.id)" ng-include="'menuItem'"></li>
</ul>
</li>
</script>
<div>
<div ni-menu="test">test1</div>
</div>
</div>
JavaScript:
var portfolioModule = angular.module('portfolioModule', []);
portfolioModule.factory('Menu', function() {
var items = [
{
"parent_id": null,
"id": 1,
"name": "foo"
}, {
"parent_id": 1,
"id": 2,
"name": "foo-bar"
}
];
this.getKids = function(parent_id) {
var result = [];
parent_id = parent_id || null;
console.log('getKids', parent_id);
angular.forEach(items, function(value) {
if (value.parent_id === parent_id) {
result.push(value);
}
});
return result;
};
return this;
}
);
portfolioModule.directive('niMenu', function(Menu) {
var niMenu;
return niMenu = {
template: '<ul ng-include="\'menuItem\'" ng-repeat="item in menu.getKids()"></ul>',
restirt: 'A',
link: function(scope, element, attrs) {
console.log('link');
scope.menu = Menu;
}
};
}
);
Working demo: http://jsfiddle.net/VanSanblch/Ng7ef.
In html I call niMenu by ni-menu. The directive has a template and a link function that put Menu-service into a scope of module. In directive's template I use Menu.getKids() and get all top level items. Later, in template that used by ng-include I call Menu.getKids(item.id) and get all children of particular item.
Everything works excellent except one small detail. If you open console then you can observe that there are much more calls of getKids than I am expected. For example, for array of two elements the number of getKids calls is nine.
Could someone explain why on earth that happens?
Upvotes: 2
Views: 1976
Reputation: 120513
Ok, so the reason it's executing more than once is because that's how the digest cycle works: it continuously executes all view expressions (like the one you pass to ngRepeat) until it expresses no change. See this answer for more information, but the takeaway is this: it will always execute at least once, but oftentimes more.
When using ngRepeat, you generally want to avoid fetching the data from a function because it negatively impacts performance; why call the same function more than once when the data never changed? A better approach is to have your controller (or in this case directive) execute your function and store the results on the scope, so your ngRepeat looks like ng-repeat="item in items"
instead of ng-repeat="item in getItems()"
.
Unfortunately, that means you have to restructure the way you have your directive working. This turns out to be a good idea anyway, because your directive can be rewritten to be a little bit simpler - you don't need any ngInclude, for example.
What you want to do is create two directives because you have two templates: one to represent the overall menu and one to represent a child item (which, in turn, can have child items). The menu
directive should repeat over the top-level menu items, drawing a menuItem
for each one. The menuItem
directive should check for children and repeat over those, if necessary, with more menuItem
s. Etc. Here's an example created by Andy Joslin of how you can accomplish a recursive directory: http://plnkr.co/edit/T0BgQR.
To implement something like this moves beyond the scope of this question, but take a stab at it and post a new question if you need help.
Good luck!
Upvotes: 6