javascript - page - Recursión con ng-repetición en angular
ng-model (10)
¿Te refieres a algo como esto? http://jsfiddle.net/uXbn6/3639/
JS
angular.module("myApp", []).controller("TreeController", [''$scope'',function($scope) {
$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": "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"
}]
}]
}];
}]);
HTML
<script type="text/ng-template" id="tree_item_renderer.html">
<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}" ng-include="''tree_item_renderer.html''"></li>
</ul>
</script>
<div ng-app="myApp" ng-controller="TreeController">
<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}" ng-include="''tree_item_renderer.html''"></li>
</ul>
</div>
Tengo la siguiente estructura de datos para los artículos en mi sidemenu, en una aplicación Angular basada en un tema de sitio web pagado. La estructura de los datos es mía, y el menú se deriva de la vista del menú original con todos los elementos en el código ultradecimal.
En 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"
},
...
]
}
]
}
];
Entonces tengo la siguiente vista parcial vinculada a ese modelo de la siguiente manera:
<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>
NOTA Es posible que haya propiedades de $scope
en los enlaces de vista que no ve en el modelo, o viceversa, pero eso se debe a que los he editado por razones de brevedad. Ahora, debido a que li
segundo nivel tampoco contiene una ul
condicional para sus propios subItems
, los subelementos bajo el Datatable
menú de datos no se procesan.
¿Cómo puedo crear una vista o una plantilla, o ambas, que se vincularán recursivamente al modelo, de modo que todos los subelementos de todos los subelementos se representen? Esto normalmente solo será de hasta cuatro niveles.
Antes de usar plantillas con ng-include
o escribir su propia directiva, sugeriría que considere utilizar una implementación de componente de árbol existente.
La razón es que a partir de su descripción, eso es exactamente lo que necesita. Tiene una estructura de datos tipo árbol jerárquico que desea mostrar. Me parece obvio que necesitas un componente de árbol.
Eche un vistazo a las siguientes implementaciones (la primera es preferible):
https://github.com/angular-ui-tree/angular-ui-tree
https://github.com/wix/angular-tree-control
http://ngmodules.org/modules/angular.treeview
Todo lo anterior requiere solo que realice un ajuste menor en su modelo o, alternativamente, use un modelo proxy.
Si insiste en implementarlo por su cuenta (y no importa cómo termine haciéndolo, esencialmente seguirá implementando un componente de árbol desde cero), sugeriría el enfoque directivo como se propuso en las respuestas anteriores. Así es como lo haría:
JS
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>''
};
}]);
HTML
<div ng-app="MyApp" ng-controller="MyCtrl">
<my-menu menu="menuItems"></my-menu>
</div>
Aquí hay un ejemplo de CodePen que funciona: http://codepen.io/eitanfar/pen/oxZrpQ
Algunas notas
- No tiene que usar 2 directivas ("my-menu", "my-menu-item"), puede usar solo 1 (simplemente reemplace la repetición ng de "my-menu-item" con su plantilla), Sin embargo, creo que es más coherente de esta manera.
- La razón por la que la solución directiva que probaste no funcionó (una suposición basada, como no he depurado tu intento), es que se ejecuta en un bucle infinito. Lo hace, ya que la vinculación se produce primero para los elementos internos. Lo que hago en mi solución sugerida, es posponer la vinculación de los sub elementos hasta que finalice la vinculación del menú principal. Cualquier desventaja que esto pueda tener puede ser superada al proporcionar referencias en el alcance (como proporciono el enlace ''menuItem'').
Espero que esto ayude.
Después de revisar estas opciones, encontré este artículo muy limpio / útil para un enfoque de ng-include que maneja bien los cambios de modelo: http://benfoster.io/blog/angularjs-recursive-templates
En resumen:
<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>
entonces
<ul>
<li ng-repeat="category in categories" ng-include="''categoryTree''"></li>
</ul>
Estoy seguro de que esto es exactamente lo que estás buscando.
Puede lograr una recursión ilimitada por ng-repetición
<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: []}];
}]);
Aquí está jsfiddle
La recursión puede ser muy complicada. Como las cosas se irán de las manos dependiendo de la profundidad de tu árbol. Aquí está mi sugerencia:
.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>
Esto asegurará que no cree sub artículos repetidamente. Puedes verlo trabajando en un jsFiddle aquí: http://jsfiddle.net/gnk8vcrv/
Si descubre que está fallando su aplicación porque tiene una gran cantidad de listas (me gustaría ver), puede ocultar las partes de la declaración if además del observador detrás de un $ timeout.
La respuesta de Rahul Arora es buena, consulte esta http://benfoster.io/blog/angularjs-recursive-templates para ver un ejemplo similar. El único cambio que haría es usar un componente en lugar de ng-include. Para ver un ejemplo, vea este Plunker :
app
.component(''recursiveItem'', {
bindings: {
item: ''<''
},
controllerAs: ''vm'',
templateUrl: ''newpartial.html''
});
Para hacer una recursión en Angular, me encantaría usar la característica básica de angularJS y es decir, la directiva.
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: ''<''
}
});
Aquí está trabajando Plunker
Puede lograr esto simplemente incluyendo una plantilla de javascript y la plantilla de inclusión utilizando ng-include
definir plantilla de javascript
<script type="text/ng-template" id="menu.html">...</script>
e incluirlo como:
<div ng-if="item.subItems.length" ng-include="''menu.html''"></div>
Ejemplo : En este ejemplo, utilicé html básico sin ninguna clase, puede usar las clases que necesite. Acabo de mostrar la estructura básica de recursión.
En 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 su intención es dibujar un menú con un nivel indefinido de sub elementos, probablemente una buena implementación es hacer una directiva .
Con una directiva, podrá asumir más control sobre su menú.
Creé un ejemplo básico con recursión total, y con usted puede ver una implementación fácil de más de un menú en la misma página y más de 3 niveles en uno de los menús, vea este plunker .
Código:
.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);
}
}
}])
Margen:
<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>
Editar
Algunas notas
Cuando se trata de tomar la decisión sobre ng-include
(que creo que es una solución bastante justa) o .directive
, .directive
hacerse al menos dos preguntas. ¿Mi fragmento de código va a necesitar algo de lógica? Si no, también puedes ir por el ng-include. Pero si va a poner más lógica en el fragmento para que sea personalizable, realice cambios en la manipulación de elementos o elementos (DOM), debe ir a la directiva. Además, un punto que me hace sentir más cómodo con la directiva es la reutilización del código que escribe, ya que en mi ejemplo puede dar más control y hacer un código más genérico, supongo que debería hacerlo si su proyecto es grande y necesita crecer. Entonces, ¿la segunda pregunta será mi código reutilizable?
Un recordatorio para una directiva limpia es que, en lugar de la template
, puede usar templateUrl
y puede proporcionar un archivo para alimentar el código html que se encuentra actualmente en la template
.
Si está usando 1.5+, ahora puede elegir usar .component
. El componente es un envoltorio de arroud .direcitve
que tiene mucho menos código repetitivo. Aquí puedes ver la diferencia.
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)
Fuente de guía angular para component
Editar
Como sugiere Mathew Berg, si no desea incluir el elemento ul, si la lista de subelementos está vacía, puede cambiar el valor de ul a este algo como esto <ul ng-if="item.subItems.length>0" my-menu="item.subItems" class="sub-menu"> </ul>
Simplemente puedes usar ng-include para hacer un parcial y llamarlo recursivamente: Parcial debería ser algo como esto:
<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>
Y tu 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>
Aquí está el plunker de trabajo http://plnkr.co/edit/9HJZzV4cgacK92xxQOr0?p=preview