lunes, 22 de diciembre de 2014

AngularJS: ng-repeat & filter

Hoy veremos cómo funciona el objeto filter en AngularJS. A priori comentar que filter no sólo se usa para filtrar los elementos de una lista en base a unos criterios sino que también se usa para dar un formato diferente a un valor. Puedes ver las diferentes aplicaciones de filter en la doc oficial de angular. En este artículo veremos ejemplos sobre como filtrar una lista (ng-repeat) en base a unos valores introducidos en uno o varios campos. Lo he enfocado desde un punto de vista práctico, explicado con algunos ejemplos sencillos que pueden ayudarte bastante a entender filter y tomar la decisión acertada en tu aplicación.

Si ya has visto algo de ng-repeat y el filtrado básico verás que por defecto puedes aplicar filter en ng-repeat y a medida que escribes en el campo de texto se va aplicando el filtro al resultado.

<input type=”text” ng-model=”query” placeHolder=”introduce un valor”>
<div ng-repeat=”ítem in ítems” | filter: query>
…
</div>
Si nos fijamos en la sintaxi de ng-repeat, podemos más o menos deducir que lo que está haciendo aquí es comparar cada uno de los atributos de ítem con el valor que hay en el campo asociado a “query” y devolver true/false en función de si encuentra dicho valor contenido en alguno de los valores asociados a las propiedades de item. Imaginemos que ítem tiene una propiedad id y otra nombre. Si escribimos “1” en el campo asociado a query buscará el valor 1 tanto en id como en nombre de cada elemento y si el ítem contiene el valor 1 la fila se seguirá mostrando, sino se dejará de ver este ítem… Este funcionamiento por defecto puede valernos en muchos casos y está bien.

Ahora imaginemos que nos interesa crear un campo de texto que no filtre por todas las propiedades del modelo.. en el ejemplo anterior si el ítem tiene id = 11 seguiría apareciendo si introducimos “1” en el campo de filtro (aunque no estemos mostrando el id por pantalla). En este caso podrías salvarlo fácilmente. Si quieres que el filtro aplique únicamente a una propiedad del modelo puedes hacerlo del siguiente modo:
<input type=”text” ng-model=”query” placeHolder=”introduce un nombre”>
<div ng-repeat=”ítem in ítems” | filter: {Name : query} >
Vamos a complicarlo un poco. Tenemos una lista de dos usuarios:
[
{
Id:1,
Name: Carlos,
HairColor: Moreno
Edad = 32
}, 
{
Id:2,
Name: Alba,
HairColor: Rubio, 
Edad = 29
}
]
Queremos tener 2 campos de texto encima de la lista, uno que filter por Name y el otro que filtre por HairColor. Bien, también podríamos hacerlo siguiendo el patrón del ejemplo anterior:
<input type=”text” ng-model=”queryNombre” placeHolder=”introduce un nombre”>
<input type=”text” ng-model=”queryPelo” placeHolder=”introduce un color de pelo”>
<div ng-repeat=”ítem in ítems” | filter: {Name : queryNombre, HairColor : queryPelo } >
Entonces si introducimos los valores “Car” en el filtro nombre sólo se debería mostrar el usuario que contenga Car en el nombre…

¿Qué pasaría si quiero filtrar, por ejemplo, aquellos usuarios que tengan más de 30 años?

Llegado este punto te diría que no sigas complicando la vista... Crea tu propio filter que para eso está. Antes de crearlo veamos un último ejemplo que nos ayudará a entender cómo funciona filter. Podríamos crearnos una función en el scope y llamarla de este modo:
$scope.Mayor30 = function(item){
 var filter = true;
 If (filterMayor30)
           filter = ítem.Edad > 30;
 return filter;
}
<input type=”checkbox” ng-model=”filterMayor30”>Mayor 30
<div ng-repeat=”ítem in ítems” | filter:  Mayor30 >
Verás que siguen apareciendo los 2 porque en el momento que ng-repeat ha filtrado el checkbox estaba desmarcado. Ahora el problema es que si marcas la casilla no se dispara de nuevo ng-repeat porque filterMayor30 no está vinculado en ningún momento a la lista.

¿Qué hay que hacer en este caso? Olvidarse de trabajar en el controlador específico de la vista y crear un filtro. Las buenas prácticas de diseño de software recomiendan crear objetos con responsabilidad única.. por tanto no uses el controlador para incluir funciones de filtrado y evita también complicar la vista como estábamos haciendo al principio del ejemplo… crea tus filters!

Angular permite crear tus propios filtros, vamos a crearnos el filtro del ejemplo anterior, las buenas prácticas recomiendan que crees un module específico para los filters:

(function () {
    'use strict';

    var filterModule = angular.module('cmFilter', []);
    
    filterModule.filter('Mayor30', function () {
        return function (items) {
            var result = [];

            if (items) {
                items.forEach(function (item) {
                    if (item.Edad > 30) {
                        result.push(item);
                    }
                });
            }

            return result;
        }
    });
}());
Tendrás que inyectar el module de filters que has creado en el mismo module donde tengas los controladores que están gestionando la vista donde necesitas el filtro. Aquí asumo que tu controlador está en el módulo principal para no liar la cosa (mala práctica):
    //AngularJS
    var app = angular.module('app', [
         //Angular modules 
         'cmFilter',
    ]);
Finalmente para usarlo en la vista se haría del siguiente modo:
<div ng-repeat="user in users | Mayor30 ">
Puedes pasar parámetros, imagínate que en vez de fijar mayor30 te interesa que el 30 sea un campo donde puedas introducir un valor. El filtro quedaría así:
    filterModule.filter('MayorX', function () {
        return function (items, edad) {
            var result = [];
            if (!edad) edad = 0;

            if (items) {
                items.forEach(function (item) {
                    if (item.Edad > edad) {
                        result.push(item);
                    }
                });
            }

            return result;
        }
    });
En vista creamos un input para recoger el valor que introducirá el usuario:
    <input type="text" ng-model="FilterEdad" placeholder="introduce un valor para edad" /> 
    <div ng-repeat="user in users | MayorX : FilterEdad| orderBy:orderProp" class="row list-item">
Siguiendo esta pauta, podrías llegar a concatenar tantos parámetros como necesites incluso puedes combinar varios filtros.

Te paso algunos links interesantes que pueden ayudarte con este tema:

Hasta aquí el artículo de hoy, posiblemente sea el último ya de este año 2014. A ver qué tal se nos presenta 2015, por lo pronto yo he decidido emprender una aventurilla y cambiar de trabajo tras 10 años en mi actual empresa... espero tocar temas interesantes en mi nuevo puesto y seguir escribiendo a menudo por areaTIC!

Hasta pronto!

1 comentario:

Anónimo dijo...

Hola, estoy empezando con Angular y me parece muy bueno el articulo, quisiera saber si existe la posibilidad de cargar la lista completa y después hacer la búsqueda y que se vaya actualizando dicha lista conforme se van ingresando lo que se desea buscar.

Publicar un comentario