Listas anidadas en AngularJS 1.2

danii . martes 3 de diciembre de 2013. a las 09:04

Listas anidadas en AngularJS

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/

Ver código en Github

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">&mdash;</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.

Etiquetas: , , ,

3 Comentarios
» Feed RSS de los Comentarios

  1. Emilio dice:

    Hola Nit

    Excelente aporte.

    Pregunta: y si quisiera corregir o actualizar el nombre de una tarea que tendría que hacer?

  2. danii dice:

    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

  3. Jesús dice:

    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

Enviar comentario