J'ai la structure de données suivante pour les éléments dans mon menu sidem, dans une application Angular basée sur un thème de site Web payant. La structure de données est la mienne et le menu est dérivé de la vue de menu d'origine avec tous les éléments de la variable ul
codés en dur.
Dans SidebarController.js
:
$scope.menuItems = [
{
"isNavItem": true,
"href": "#/dashboard.html",
"text": "Dashboard"
},
{
"isNavItem": true,
"href": "javascript:;",
"text": "AngularJS Features",
"subItems": [
{
"href": "#/ui_bootstrap.html",
"text": " UI Bootstrap"
},
...
]
},
{
"isNavItem": true,
"href": "javascript:;",
"text": "jQuery Plugins",
"subItems": [
{
"href": "#/form-tools",
"text": " Form Tools"
},
{
"isNavItem": true,
"href": "javascript:;",
"text": " Datatables",
"subItems": [
{
"href": "#/datatables/managed.html",
"text": " Managed Datatables"
},
...
]
}
]
}
];
Ensuite, j'ai la vue partielle suivante liée à ce modèle comme suit:
<ul class="page-sidebar-menu" data-keep-expanded="false" data-auto-scroll="true" data-slide-speed="200" ng-class="{'page-sidebar-menu-closed': settings.layout.pageSidebarClosed}">
<li ng-repeat="item in menuItems" ng-class="{'start': item.isStart, 'nav-item': item.isNavItem}">
<a href="{{item.href}}" ng-class="{'nav-link nav-toggle': item.subItems && item.subItems.length > 0}">
<span class="title">{{item.text}}</span>
</a>
<ul ng-if="item.subItems && item.subItems.length > 0" class="sub-menu">
<li ng-repeat="item in item.subItems" ng-class="{'start': item.isStart, 'nav-item': item.isNavItem}">
<a href="{{item.href}}" ng-class="{'nav-link nav-toggle': item.subItems && item.subItems.length > 0}">
<span class="title">{{item.text}}</span>
</a>
</li>
</ul>
</li>
</ul>
NOTEIl peut y avoir des propriétés $scope
dans les liaisons de vue que vous ne voyez pas dans le modèle, ou inversement, mais c'est parce que je les ai modifiées par souci de concision. Maintenant, comme le second niveau li
ne contient pas également une ul
conditionnelle pour sa propre subItems
, les sous-éléments de l'élément de menu Datatable
ne sont pas rendus.
Comment puis-je créer une vue ou un modèle, ou les deux, qui seront liés de manière récursive au modèle, de sorte que tous les sous-éléments de tous les sous-éléments soient rendus? Ce ne sera normalement que jusqu'à quatre niveaux.
Vous pouvez simplement utiliser ng-include pour créer un partiel et l'appeler de manière récursive: Partial devrait ressembler à ceci:
<ul>
<li ng-repeat="item in item.subItems">
<a href="{{item.href}}" ng-class="{'nav-link nav-toggle': item.subItems && item.subItems.length > 0}">
<span class="title">{{item.text}}</span>
</a>
<div ng-switch on="item.subItems.length > 0">
<div ng-switch-when="true">
<div ng-init="subItems = item.subItems;" ng-include="'partialSubItems.html'"></div>
</div>
</div>
</li>
</ul>
Et votre html:
<ul class="page-sidebar-menu" data-keep-expanded="false" data-auto-scroll="true" data-slide-speed="200" ng-class="{'page-sidebar-menu-closed': settings.layout.pageSidebarClosed}">
<li ng-repeat="item in menuItems" ng-class="{'start': item.isStart, 'nav-item': item.isNavItem}">
<a href="{{item.href}}" ng-class="{'nav-link nav-toggle': item.subItems && item.subItems.length > 0}">
<span class="title">{{item.text}}</span>
</a>
<ul ng-if="item.subItems && item.subItems.length > 0" class="sub-menu">
<li ng-repeat="item in item.subItems" ng-class="{'start': item.isStart, 'nav-item': item.isNavItem}">
<a href="{{item.href}}" ng-class="{'nav-link nav-toggle': item.subItems && item.subItems.length > 0}">
<span class="title">{{item.text}}</span>
</a>
<div ng-switch on="item.subItems.length > 0">
<div ng-switch-when="true">
<div ng-init="subItems = item.subItems;" ng-include="'newpartial.html'"></div>
</div>
</div>
</li>
</ul>
</li>
</ul>
Voici le travail de travail http://plnkr.co/edit/9HJZzV4cgacK92xxQOr0?p=preview
Vous pouvez y parvenir simplement en incluant un modèle javascript et un modèle include en utilisant ng-include
définir un modèle javascript
<script type="text/ng-template" id="menu.html">...</script>
et l'inclure comme:
<div ng-if="item.subItems.length" ng-include="'menu.html'"></div>
Exemple: Dans cet exemple, j'ai utilisé le langage HTML de base sans classe. Vous pouvez utiliser les classes à votre guise. Je viens de montrer la structure de base de la récursivité.
Dans html:
<ul>
<li ng-repeat="item in menuItems">
<a href="{{item.href}}">
<span>{{item.text}}</span>
</a>
<div ng-if="item.subItems.length" ng-include="'menu.html'"></div>
</li>
</ul>
<script type="text/ng-template" id="menu.html">
<ul>
<li ng-repeat="item in item.subItems">
<a href="{{item.href}}">
<span>{{item.text}}</span>
</a>
<div ng-if="item.subItems.length" ng-include="'menu.html'"></div>
</li>
</ul>
</script>
Si votre intention est de dessiner un menu avec un nombre indéfini de sous-éléments, une bonne implémentation consiste probablement à créer une directive .
Avec une directive, vous serez en mesure d’assumer plus de contrôle sur votre menu.
J'ai créé un exemple basique complet avec récursion, avec lequel vous pouvez voir une implémentation facile de plusieurs menus sur la même page et de plus de 3 niveaux sur l'un des menus, voir ceci plunker .
Code:
.directive('myMenu', ['$parse', function($parse) {
return {
restrict: 'A',
scope: true,
template:
'<li ng-repeat="item in List" ' +
'ng-class="{\'start\': item.isStart, \'nav-item\': item.isNavItem}">' +
'<a href="{{item.href}}" ng-class="{\'nav-link nav-toggle\': item.subItems && item.subItems.length > 0}">'+
'<span class="title"> {{item.text}}</span> ' +
'</a>'+
'<ul my-menu="item.subItems" class="sub-menu"> </ul>' +
'</li>',
link: function(scope,element,attrs){
//this List will be scope invariant so if you do multiple directive
//menus all of them wil now what list to use
scope.List = $parse(attrs.myMenu)(scope);
}
}
}])
Balisage:
<ul class="page-sidebar-menu"
data-keep-expanded="false"
data-auto-scroll="true"
data-slide-speed="200"
ng-class="{'page-sidebar-menu-closed': settings.layout.pageSidebarClosed}"
my-menu="menuItems">
</ul>
Modifier
Quelques notes
Quand il s'agit de prendre une décision concernant ng-include
(je pense que c'est une solution assez juste) ou .directive
, vous devez vous poser au moins deux questions. Mon fragment de code va-t-il avoir besoin de logique? Sinon, vous pouvez également vous contenter de ng-include . Mais si vous voulez mettre plus de logique dans le fragment afin de le rendre personnalisable, apportez des modifications à la manipulation de l'élément ou attr (DOM), vous devriez opter pour la directive . Un autre point qui me rend plus à l'aise avec la directive est la réutilisabilité du code que vous écrivez, car dans mon exemple, vous pouvez donner plus de contrôle à un contrôleur et en créer un plus générique, je suppose que vous devriez y aller si votre projet est grand et doit grandir. Donc, la deuxième question est-ce que mon code va être réutilisable?
Un rappel pour une directive de nettoyage est qu'au lieu de template
, vous pouvez utiliser templateUrl
et vous pouvez donner un fichier pour alimenter le code html actuellement présent dans template
.
Si vous utilisez 1.5+, vous pouvez maintenant choisir d'utiliser .component
. Le composant est un emballage .direcitve
qui contient beaucoup moins de code standard. Ici vous pouvez voir la différence.
Directive Component
bindings No Yes (binds to controller)
bindToController Yes (default: false) No (use bindings instead)
compile function Yes No
controller Yes Yes (default function() {})
controllerAs Yes (default: false) Yes (default: $ctrl)
link functions Yes No
multiElement Yes No
priority Yes No
require Yes Yes
restrict Yes No (restricted to elements only)
scope Yes (default: false) No (scope is always isolate)
template Yes Yes, injectable
templateNamespace Yes No
templateUrl Yes Yes, injectable
terminal Yes No
transclude Yes (default: false) Yes (default: false)
Guide angulaire source pour composant
Modifier
Comme suggéré par Mathew Berg, si vous souhaitez ne pas inclure l'élément ul si la liste des sous-éléments est vide, vous pouvez modifier le paramètre ul pour qu'il ressemble à ceci <ul ng-if="item.subItems.length>0" my-menu="item.subItems" class="sub-menu"> </ul>
Après avoir examiné ces options, j’ai trouvé cet article très propre/utile pour une approche ng-include qui gère bien les modifications de modèle: http://benfoster.io/blog/angularjs-recursive-templates
En résumé:
<script type="text/ng-template" id="categoryTree">
{{ category.title }}
<ul ng-if="category.categories">
<li ng-repeat="category in category.categories" ng-include="'categoryTree'">
</li>
</ul>
</script>
puis
<ul>
<li ng-repeat="category in categories" ng-include="'categoryTree'"></li>
</ul>
Avant d’utiliser des modèles avec ng-include
ou d’écrire votre propre directive, je vous suggère d’envisager d’utiliser une implémentation de composant d’arbre existante.
La raison en est que d'après votre description, c'est exactement ce dont vous avez besoin. Vous souhaitez afficher une structure de données arborescente hiérarchique. Il me semble évident que vous avez besoin d'un composant d'arbre.
Jetez un coup d'œil aux implémentations suivantes (la première préférable):
https://github.com/angular-ui-tree/angular-ui-tree
https://github.com/wix/angular-tree-control
http://ngmodules.org/modules/angular.treeview
Tout ce qui précède nécessite seulement que vous apportiez une modification mineure à votre modèle ou que vous utilisiez un modèle de proxy.
Si vous insistez pour le mettre en œuvre vous-même (et quelle que soit la façon dont vous le réalisez, vous allez toujours implémenter une composante arborescente à partir de rien), je suggérerais l'approche directive, telle que proposée dans les réponses précédentes. Voici comment je le ferais:
var app=angular.module('MyApp', []);
app.controller('MyCtrl', function($scope, $window) {
$scope.menuItems = [
{
"isNavItem": true,
"href": "#/dashboard.html",
"text": "Dashboard"
},
{
"isNavItem": true,
"href": "javascript:;",
"text": "AngularJS Features",
"subItems": [
{
"href": "#/ui_bootstrap.html",
"text": " UI Bootstrap"
}
]
},
{
"isNavItem": true,
"href": "javascript:;",
"text": "jQuery Plugins",
"subItems": [
{
"href": "#/form-tools",
"text": " Form Tools"
},
{
"isNavItem": true,
"href": "javascript:;",
"text": " Datatables",
"subItems": [
{
"href": "#/datatables/managed.html",
"text": " Managed Datatables"
}
]
}
]
}];
});
app.directive('myMenu', ['$compile', function($compile) {
return {
restrict: 'E',
scope: {
menu: '='
},
replace: true,
link: function(scope, elem, attrs) {
var items = $compile('<my-menu-item ng-repeat="item in menu" menu-item="item"></my-menu-item>')(scope);
elem.append(items);
},
template: '<ul class="page-sidebar-menu" data-keep-expanded="false" data-auto-scroll="true" data-slide-speed="200" ng-class="{\'page-sidebar-menu-closed\': settings.layout.pageSidebarClosed}"></ul>'
};
}]);
app.directive('myMenuItem', [function() {
return {
restrict: 'E',
scope: {
menuItem: '='
},
replace: true,
template: '<li ng-class="{\'start\': item.isStart, \'nav-item\': item.isNavItem}"><a href="{{menuItem.href}}" ng-class="{\'nav-link nav-toggle\': menuItem.subItems && menuItem.subItems.length > 0}"> <span class="title">{{menuItem.text}}</span></a><my-menu menu="menuItem.subItems"></my-menu></li>'
};
}]);
<div ng-app="MyApp" ng-controller="MyCtrl">
<my-menu menu="menuItems"></my-menu>
</div>
Voici un exemple de travail avec CodePen: http://codepen.io/eitanfar/pen/oxZrpQ
J'espère que cela t'aides.
Je suis sûr que c'est exactement ce que vous recherchez -
Vous pouvez obtenir une récursion illimitée avec ng-repeat
<script type="text/ng-template" id="tree_item_renderer.html">
{{data.name}}
<button ng-click="add(data)">Add node</button>
<button ng-click="delete(data)" ng-show="data.nodes.length > 0">Delete nodes</button>
<ul>
<li ng-repeat="data in data.nodes" ng-include="'tree_item_renderer.html'"></li>
</ul>
angular.module("myApp", []).
controller("TreeController", ['$scope', function($scope) {
$scope.delete = function(data) {
data.nodes = [];
};
$scope.add = function(data) {
var post = data.nodes.length + 1;
var newName = data.name + '-' + post;
data.nodes.Push({name: newName,nodes: []});
};
$scope.tree = [{name: "Node", nodes: []}];
}]);
Voici jsfiddle
La récursion peut être très délicate. Comme les choses vont devenir incontrôlables en fonction de la profondeur de votre arbre. Voici ma suggestion:
.directive('menuItem', function($compile){
return {
restrict: 'A',
scope: {
menuItem: '=menuItem'
},
templateUrl: 'menuItem.html',
replace: true,
link: function(scope, element){
var watcher = scope.$watch('menuItem.subItems', function(){
if(scope.menuItem.subItems && scope.menuItem.subItems.length){
var subMenuItems = angular.element('<ul><li ng-repeat="subItem in menuItem.subItems" menu-item="subItem"></li></ul>')
$compile(subMenuItems)(scope);
element.append(subMenuItems);
watcher();
}
});
}
}
})
HTML:
<li>
<a ng-href="{{ menuItem.href }}">{{ menuItem.text }}</a>
</li>
Cela garantira qu'il ne crée pas de sous-éléments à plusieurs reprises. Vous pouvez le voir fonctionner dans un jsFiddle ici: http://jsfiddle.net/gnk8vcrv/
Si vous rencontrez le blocage de votre application parce que vous avez un grand nombre de listes (je serais intéressé de le voir), vous pouvez masquer les parties de l'instruction if à côté de l'observateur derrière un délai d'attente de $.
Afin de faire de la récursion en angulaire, j'aimerais utiliser les fonctionnalités de base de angularJS et i.e directive.
index.html
<rec-menu menu-items="menuItems"></rec-menu>
recMenu.html
<ul>
<li ng-repeat="item in $ctrl.menuItems">
<a ng-href="{{item.href}}">
<span ng-bind="item.text"></span>
</a>
<div ng-if="item.menuItems && item.menuItems.length">
<rec-menu menu-items="item.menuItems"></rec-menu>
</div>
</li>
</ul>
recMenu.html
angular.module('myApp').component('recMenu', {
templateUrl: 'recMenu.html',
bindings: {
menuItems: '<'
}
});
Ici travaille Plunker
La réponse de Rahul Arora est bonne, voir cet article blog pour un exemple similaire. Le seul changement que je ferais serait d'utiliser un composant au lieu de ng-include. Pour un exemple, voyez ceci Plunker :
app
.component('recursiveItem', {
bindings: {
item: '<'
},
controllerAs: 'vm',
templateUrl: 'newpartial.html'
});