A la hora de diseñar una aplicación o software, es bastante común el caso de necesitar realizar un listado de datos anidados o jerarquizados. Por ejemplo: una lista de equipos pertenecientes a una liga y que incluye el entrenador y los jugadores de cada equipo, o un listado de autores con sus libros, que puede tener un nivel más de anidamiento si cada libro incluye, por ejemplo, una lista con cada una de las ediciones que ha tenido.
Desde la versión del framework 1.2, AngularJS nos provee con dos nuevas directivas diseñadas para facilitarnos esto. Estas dos nuevas directivas son ng-repeat-start y ng-repeat-end. Utilizando ambas junto con la clásica ng-repeat podemos conseguir una visualización de datos anidados sencilla pero realmente potente y adaptable.
Las directivas ng-repeat, ng-repeat-start y ng-repeat-end
- ng-repeat
Una de las primeras directivas con las que todo programador se familiariza en el framework AngularJS es ng-repeat. Cumple las funciones de un loop que recorre una colección de datos (array u objeto) repitiendo la etiqueta HTML sobre el que está aplicado (y sus contenidos ) por cada elemento en dicha colección. - ng-repeat-start
Esta directiva extiende el ámbito del loop a un bloque que se extiende hasta la directiva asociada ng-repeat-end. De esta manera, repetirá todo el código HTML (incluyendo la etiqueta HTML sobre el que esté aplicada) hasta el final del bloque. - ng-repeat-end
Marca el final del bloque ng-repeat-start
Mini App de ejemplo: To Do web app con categorías
Para demostrar el poder de estas directivas hemos decidido construir una sencilla web app de To Do o lista de tareas, con la funcionalidad adicional de que las tareas estarán agrupadas en categorías. Perfecto para listas las tareas y categorías de forma anidada gracias a ng-repeat-start y ng-repeat-end 🙂
Aprovechando la potencia de AngularJS para data-binding, hemos añadido la posibilidad de crear categorías y tareas asignadas a éstas, además de poder marcar tareas como finalizadas (o no) y eliminarlas. ¡Una web app sencilla pero funcional!
http://www.lostiemposcambian.com/blog/posts/angular-nested-lists/
El modelo de datos
Para esta demo hemos optado por un modelo de datos simplificado al máximo, vamos a tener dos tipos de datos:
- Categorías: Simplemente tienen un nombre y una lista de tareas asociadas. Además, para la visualización de los datos hemos añadido un booleano para marcar si la debemos mostrar expandida o no.
- Tareas: Nombre de la tarea y un booleano que indica si está finalizada ya finalizada.
Como es obvio, las tareas se encuentran anidadas dentro de las categorías. Este es nuestro modelo de datos inicial de ejemplo, en formato JSON:
function DataModel() { this.data = [ { name: 'Personal', expanded: true, items: [ { name: 'Walk dog', completed: false }, { name: 'Write blog post', completed: true }, { name: 'Buy milk', completed: false }, ] }, { name: 'Work', expanded: false, items: [ { name: 'Ask for holidays', completed: false } ] }, { name: 'Books to read', expanded: false, items: [ { name: 'War and peace', completed: false }, { name: '1Q84', completed: false }, ] } ]; }
La vista
Es en la vista donde toda la magia de las directivas ng-repeat-start y ng-repeat-end sucede. Hemos marcado con comentarios los bloques definidos por estas directivas, pero creemos que viendo el resultado final es fácil entender qué es lo que cada bloque está renderizando al recorrer el array del modelo de datos.
<!-- Repeat start, beggining of category group --> <div ng-repeat-start="category in data" class="animate-repeat-category"> <button class="btn btn-primary btn-block btn-sm" ng-click="toggleCategory(category)"> {{ category.name }} <span class="expand-indicator" ng-show="category.expanded">—</span> <span class="expand-indicator" ng-show="!category.expanded">+</span> </button> </div> <!-- Item group inside each category --> <div class="list-items animate-show-item" ng-show="category.expanded"> <ul> <li ng-repeat="item in category.items" class="animate-repeat-item"> <span ng-class="{done:item.completed}">{{ item.name }}</span> <button ng-show="!item.completed" ng-click="completeTask(item)" class="btn btn-success btn-xs">done</button> <button ng-show="item.completed" ng-click="uncompleteTask(item)" class="btn btn-warning btn-xs">reset</button> <button ng-click="deleteTask(category, item)" class="btn btn-danger btn-xs">del</button> </li> </ul> <div class="no-items"><small ng-show="category.expanded && category.items.length==0">NO ITEMS!</small></div> </div> <!-- Repeat end, closing category group --> <div ng-repeat-end> <button ng-show="category.expanded" class="btn btn-primary btn-block btn-sm animate-show-item" disabled="disabled">/ {{ category.name }}</button> <hr> </div>
También en la vista hemos definido las clases CSS que servirán de base para las animaciones de entrada y salida de los elementos. Y es que mucho ha cambiado en pocos meses desde que escribimos nuestro post de Animaciones y transiciones en AngularJS y desde la versión 1.2 del framework es más fácil que nunca añadir estas animaciones tan atractivas visualmente a nuestras directivas. Por ejemplo, tras asignar el nombre de clase .animate-show-item a los elementos items que tienen transiciones de entrada y salida, tan sólo hemos de definir las siguientes clases CSS que definen el comportamiento de dichas transiciones, en este caso desplazarse hacia arriba 15 píxeles y adquirir transparencia alfa:
/* Animations */ .animate-show-item.ng-hide-add, .animate-show-item.ng-hide-remove { -webkit-transition:0.33s ease all; transition:0.5s linear all; /* remember to add this */ display:block!important; position: relative; top: 0; opacity:1; } .animate-show-item.ng-hide { top: -15px; opacity:0; }
!Y AngularJS se encarga del resto! 🙂 Eso sí, no olvidemos cargar el módulo ngAnimate y añadirlo a la lista de dependencias de nuestra aplicación. Entre que nos decidimos a escribir un post actualizado sobre este nuevo sistema de animaciones y transiciones en AngularJS 1.2 (¡se admiten sugerencias!), os recomendamos una vez más pegarle un ojo al tremendo blog year of moo donde podemos encontrar un magnífico overview de las novedades.
En resumen, estas directivas ng-repeat-start y ng-repeat-end nos parecen adiciones más que interesantes al framework, ya que añaden aún más potencia a la que ya era una de las características más atractivas de AngularJS: el listado, visualización y manipulación de colecciones de datos del modelo en la vista.
Hola Nit
Excelente aporte.
Pregunta: y si quisiera corregir o actualizar el nombre de una tarea que tendría que hacer?
Buena pregunta Emilio! Se me ocurre que la mejor manera sería permitir al usuario hacer double-click sobre el nombre y que entonces el nodo se convirtiera en un text input. Y además si lo programamos mediante una directiva de angular, podremos reutilizar este comportamiento tan útil en otras partes de este u otro proyecto mmmh *puts programming hat on
Saludos, tengo una duda,
como hago si me quiero listar los elementos existentes
En una base de datos, pero a su vez desearía
Traerme los elementos de un campo específico que
Sólo se repita para el elemento padre que posea elemnetos hijos.
Es decir tengo una tabla fruteria
[Frutas]
Fresas
Manzanas
[Verdurad]
Papa
Yuca
Ñame
[hortalizas]
Zanahoria
Brócoli
Segun su ejemplo tendira que algo similar esto:
Fruteria =[{namef: ‘frutas’}, {itemf:[{nameitms: ‘fresa’} etc…
El problema es a cuando hago la consulta a la
Base de datos viene el array en formato JSON
Y no me permite procesar el ng-repeat