Usos más detallados de la directiva ngRepeat para filtrar, ordenar los elementos y acceder a variables embutidas que nos dan información sobre las repeticiones.
La directiva ngRepeat de AngularJS es una de esas cosas que llaman la atención, por la sencillez y rapidez con la que se puede implementar un bucle que nos repite una plantilla HTML, pero sobre todo por su potencia.
La directiva ya la hemos usado en diversos ejemplos anteriormente, incluso tenemos un artículo anterior donde se introdujeron algunos detalles básicos de ng-repeat para empezar a usarla. Ahora queremos profundizar un poco más en otros usos que resultarán muy útiles para cualquier tipo de aplicación web.
En la documentación de la directiva ngRepeat encuentras muchos datos útiles, aqui te vamos a resumir algunos y vamos a realizar un ejemplo que lo ponga todo en práctica.
Sintaxis ngRepeat
Nos tenemos que familiarizar primero con la sintaxis usada para especificar el comportamiento del bucle en ngRepeat. Aunque ya lo hemos visto en cantidad de ejemplos, comencemos por repasar la sintaxis básica.
<p ng-repeat="elem in elementos">
{{ elem }}
</p>
En la directiva indicamos el recorrido a una colección y la variable donde vamos a tener el elemento actual: "elem in elementos". En este caso "elementos" es la colección que vamos a recorrer en el bucle y "elem" es la variable donde vamos a tener el elemento actual en cada repetición.
Pero eso no es lo único que se puede marcar en esta directiva, también podremos especificar otras cosas, colocando el caracter "|" (tubería / pipe) como separador.
ng-repeat="elem in elementos | filtros | ordenacion"
Técnicamente podemos decir que dentro del HTML de tu repetición tendrás un scope local, donde encontrarás el elemento actual sobre el que estás iterando, así como otras variables útiles que veremos también, como $index que nos devuelve el índice actual en el recorrido.
Filtrado en ngRepeat
El filtrado nos sirve para hacer una búsqueda dentro de los elementos de la colección que tenemos en un array o en un objeto en nuestro modelo.
Se indica con la palabra filter, seguida por ":" y la cadena o variable donde está la cadena que nos sirve para filtrar.
ng-repeat="cerveza in vm.cervezas | filter:'pale' "
Esto nos mostrará solo aquellas cervezas que tienen la palabra "pale" escrita por algún lado. Por supuesto, en lugar de un literal de cadena para el filtrado, puedes usar una variable de tu modelo.
ng-repeat="cerveza in vm.cervezas | filter:vm.filtroCliente"
Ahora utilizarás el contenido de la variable vm.filtroCliente para mostrar aquellos elementos que corresponda.
Orden de los elementos en la repetición
Ahora veamos cómo expresar el orden de visualización de los elementos en la repetición. Para ello usamos la palabra "ordenBy", seguido de ":" y el campo sobre el que se debe ordenar. Opcionalmente colocamos después si el orden es ascendente o descendente.
ng-repeat="cerveza in vm.cervezas | orderBy:'name':true"
En este caso ordenamos los elementos por el campo "name". Lógicamente debe de ser un atributo del objeto que estás recorriendo en la colección. Luego opcionalmente colocamos un boleano para indicar el orden. De manera predeterminada el orden es ascendente. Si el boleano es true, entonces el orden se hace descendente y si es false, entonces es ascendente.
Nuevamente, el orden se indicará habitualmente a partir de variables de Javascript o del modelo de nuestra vista. En un código que podría ser como este:
ng-repeat="cerveza in vm.cervezas | orderBy:vm.campo:vm.orden"
Variables de la repetición en el scope local
Como hemos dicho existe un scope local que nos expone el elemento actual de nuestra repetición. Pero además tenemos una serie de variables que AngularJS nos ofrece de manera adicional. Son variables que resultarán muy útiles para tus necesidades habituales en recorridos.
Por ejemplo en el scope actual tenemos la variable $index, que nos indica el índice actual en la iteración. El primer índice recuerda que es cero, el segundo será uno, etc. Para mostrar ese índice hacemos como con cualquier otro dato que exista en el scope, meterlo dentro de una expresión encerrada con dobles llaves.
<p ng-repeat="elem in elementos">
El elemento actual es {{ $index }} y su valor es {{ elem }}
</p>
Junto con el $index, este es el conjunto de variables inyectadas automáticamente en el scope local:
- $index, numércico. Es el índice de la iteración.
- $first, boleano, tendrá el valor true para el primer elemento de la iteración.
- $middle, boleano, será true en los elementos que no sean primero o último.
- $last, boleano, solo será true en el último elemento.
- $even, boleano, será true en caso que ese elemento sea impar en la lista de repetición.
- $odd, boleano, será true en caso que el elemento sea de índice par.
Ejercicio de repetición con ng-repeat con filtrado, orden y variables de scope local
Ahora vamos a ver el código de un ejercicio que nos facilite practicar con estos sistemas para personalizar la repetición de un template en AngularJS.
El ejemplo es parecido al ejercicio anterior donde conocimos JSONP en AngularJS, solo que ahora aplicamos las posibilidades de ngRepeat vistas en este artículo. Para ello hemos incorporado un par de controles especiales, que nos permiten:
- Escribir un texto para realizar el filtrado
- Seleccionar el campo sobre el que queremos la ordenación y si ésta debe ser ascendente (orden alfabético porque se trata de campos que tienen cadenas) o descendente.
<div ng-app="apiApp" ng-controller="apiAppCtrl as vm">
<h1>Pruebo Ajax con JSONP</h1>
<p>
Busca cerveza:
<input type="text" ng-model="vm.nombre"> <input type="button" value="Buscar" ng-click="vm.buscaCervezas()">
</p>
<aside>
<h2>Filtra:</h2>
<input type="text" ng-model="vm.filtroCliente">
<h2>Orden</h2>
<p>
<button ng-click="vm.orden=false">Alfabetico</button>
<br />
<button ng-click="vm.orden=true">Contrario</button>
</p>
<p>
<input type="radio" name="campo" ng-model="vm.campo" value="name"> Nombre
<br />
<input type="radio" name="campo" ng-model="vm.campo" value="description"> Descripción
</p>
</aside>
<section>
<ul>
<li ng-repeat="cerveza in vm.cervezas | filter:vm.filtroCliente | orderBy:vm.campo:vm.orden" ng-class="{even: $even, odd: $odd}">
<span>{{$index +1}}.- {{cerveza.name}},</span> {{ cerveza.description }}
</li>
</ul>
</section>
</div>
Para el texto del filtrado usamos el INPUT type text que hemos marcado con ng-model="vm.filtroCliente".
Para el orden tenemos dos tipos de controles, diferentes para poder practicar con más cosas. Tenemos un par de botones que nos permiten marcar orden alfabético o contrario, que tienen sus correspondientes ng-click para definir comportamientos. Por otra parte tenemos un par de campos INPUT de radio para definir si queremos que se ordene por el nombre de la cerveza o su campo descripción.
Pero sobre todo te tienes que fijar en el listado de cervezas que realizamos con una lista UL y sus correspondientes LI. Echa un vistazo a la directiva y al template que engloba:
<li ng-repeat="cerveza in vm.cervezas | filter:vm.filtroCliente | orderBy:vm.campo:vm.orden" ng-class="{even: $even, odd: $odd}">
<span>{{$index +1}}.- {{cerveza.name}},</span> {{ cerveza.description }}
</li>
Aquí tenemos varios elementos que destacar:
- La repetición se hace con la colección que tenemos en vm.cervezas (que se puebla con una llamada Ajax con JSONP
- Dentro de nuestro template, el LI y todo su contenido, tendremos disponible la cerveza actual en una variable llamada "cerveza" del scope actual.
- Para el filtrado usamos el contenido del campo de texto con ng-model="vm.filtroCliente".
- Para el orden usamos tanto el valor vm.campo como el vm.orden que se sacan de los botones y los radiobuttons.
- Además puedes ver otra directiva nueva hasta este momento en el Manual de AngularJS que es ng-class. Nos sirve para indicarle la class de CSS que queremos aplicar a un elemento HTML. En este caso usamos los valores $even y $odd del scope local (datos creados automáticamente por Angular) para colocarle la clase adecuada. De esta manera conseguimos el típico estilo de filas coloreadas como una cebra.
- Por último puedes reparar dentro del contenido de los LI que estamos usando el $index, que es otra variable del scope local, que nos viene de fábula para numerar las filas de los elementos de la lista.
El Javascript tendrá una pinta como la siguiente:
angular
.module('apiApp', [])
.controller('apiAppCtrl', controladorPrincipal);
function controladorPrincipal($scope, $http){
var vm=this;
vm.orden = false;
vm.campo = "name";
var url = "http://api.openbeerdatabase.com/v1/beers.json?callback=JSON_CALLBACK";
if(vm.nombre){
url += "&query=" + vm.nombre
}
vm.buscaCervezas = function(){
$http.jsonp(url).success(function(respuesta){
console.log("res:", respuesta);
vm.cervezas = respuesta.beers;
});
}
}
Realmente en este Javascript hay poco que necesites aprender, que no hayas visto ya en artículos anteriores. Te podrá interesar la inicialización de datos en el scope qe encuentras en las primeras líneas de la función del controlador.
Aunque para la materia que nos trata no importa demasiado, sí que necesitarás unos estilos CSS para formatear un poco la presentación de ese HTML, de manera que quede bonito y puedas apreciar algunas cosas como los colores de fondo de tipo cebra para los elementos de la lista.
body{ font-family: sans-serif;}
li{
font-size: 0.8em;
margin-bottom: 10px;
padding: 10px;
}
li span{
font-weight: bold;
display: block;
font-size: 1.2em;
}
aside{
width: 200px;
float: right;
padding: 20px;
display: table-cell;
}
aside h2{
margin-bottom: 3px;
}
section{
display: table-cell;
}
li.even{
background-color: #d5d5d5;
}
li.odd{
background-color: #d5d5ff;
}
Eso es todo de momento, espero que puedas aprovechar ya mismo algunas de estas potentes características de ngRepeat. Como habrás encontrado, esta directiva permite hacer cosas impactantes sin escribir nada de código, algo que apreciarás sobre todo si tienes algo de experiencia en Javascript o librerías como jQuery. Pero OJO, esa misma potencia puede que resulte perjudicial para tu aplicación si se usa sin control, nunca intentes usar ng-repeat con conjuntos masivos de datos, a lo sumo con elementos de 100 en 100, aplicando técnicas como la paginación para reducir los elementos en la vista.
Alberto Basalo
Alberto Basalo es experto en Angular y otras tecnologías basadas en Javascript,...