jueves, 1 de septiembre de 2016

[Iionic 2 (Angular 2 y TypeScript)] Cómo filtrar una lista mediante un select

Buenas tardes,



Hoy hago una mini-guía de como hacer un filtro en Ionic 2 (y/o en su defecto Angular 2 y TypeScript) para filtrar datos de un array que construye una lista a partir de un select.


Antes que nada, yo enfoco el tutorial a ionic, pero cabe aclarar que sirve para cualquier proyecto con Angular 2 y TypeScript. Así que comencemos.
Cabe aclarar los siguientes conceptos:

Pipes: un  pipe (o tubería en español) es un modo de pasar datos por medio de alguna función para tranformarlos, modificarlos o simplemente visualizarlos a nuestro antojo. Un pipe se reconoce fácilmente por su carácter de uso que es la barra vertical "|". Así para poner un ejemplo en bash, cuando tenemos un archivo grande y queremos ver su contenido usamos cat por ejemplo, sin embargo al ser tan grande imprimirá todo y veremos probablemente solo las últimas lineas teniendo que hacer scroll para leer las primeras; una posible solución es usar more o less en vez de cat, pero para éste ejemplo usaremos cat y redireccionaremos la salida del texto a través de un pipe a more o less así: cat archivo_grante.txt | more o cat archivo_grante.txt | less y obtendremos el mismo resultado. 

Ahora sí, supongamos que tenemos la siguiente clase, y a su vez la siguiente vista:

Clase: pruebafiltro.ts
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

@Component({
    templateUrl: 'build/pages/pruebafiltro/pruebafiltro.html'
})
export class PruebaFiltroPage {
    private tareas: any;
    private usuarios: any;

    constructor(private nav: NavController) {
        this.tareas = this.CargarTareas();
        this.usuarios = this.CargarUsuarios();
    }

    private CargarTareas() : any {
        return [
            { "usuario_id": 1, "tarea": "Programar" },
            { "usuario_id": 2, "tarea": "Relajarse" },
            { "usuario_id": 1, "tarea": "Ir al baño" },
            { "usuario_id": 2, "tarea": "Ver porno" },
            { "usuario_id": 1, "tarea": "Ver película o serie" },
            { "usuario_id": 2, "tarea": "Hablar con los muertos" }
        ];
    }

    private CargarUsuarios() : any {
        return [
            { "id": 1, "nombre": "Kevin Mitnick" },
            { "id": 2, "nombre": "El Bananero" }
        ];
    }
}
Vista: pruebafiltro.html
<ion-content padding="">
    <ion-select>
        <ion-option selected="selected" value="*">Todos</ion-option>
        <ion-option ngfor="let usu of usuarios" value="{{ usu.id }}">{{ usu.nombre }}</ion-option>
    </ion-select>
    <ion-list>
        <ion-item ngfor="let tar of tareas">
            <h2>{{ tar.tarea }}</h2>
        </ion-item>
    </ion-list>
</ion-content>


Bien, cómo vemos en está parte de la estamos cargando usuarios por medio de un ngFor que lee la variable usuarios de la clase. e igualmente estamos creando un ngFor para las tareas que se listan todas. Hasta éste momento solo veremos el select con los datos de los 2 usuario e igualmente el listado de las tareas, así que empecemos a crear el filtro.

Para ello vamos a crearnos una clase que extiende de PipeTransform, en mi caso en mi directorio app (por orden) he creado un esquema de subdirectorios así: pipes/filtrotareas y dentro de filtro tareas el archivo en cuestión llamado filtrotareas.ts así:

import { Pipe, PipeTransform } from "@angular/core";

@Pipe({
    name: "filtroTareas",
    pure: false
})
export class FiltroTareasPipe implements PipeTransform {

    transform(items: Array, conditions: { [field: string]: any }): Array {
        if (typeof items.filter !== "undefined") {
            return items.filter(item => {
                for (let field in conditions) {
                    if (item[field] !== conditions[field] && conditions[field] != "*") {
                        return false;
                    }
                }
                return true;
            });
        }
    }
}

Paso a explicar; primero que todo importamos las clases Pipe y la interface PipeTransform desde @angular/core. Luego iniciamos la clase con el decorador @Pipe para indicar que ésta cumplirá la función de un pipe y como parámetros le pasamos el nombre e indicamos que no es pura.

Luego creamos la clase que implementa el la interface PipeTransform y dentro de ella la función transform (que ya está declarada dentro de PipeTransform de la cual implementamos) con los parámetros que necesitamos. En éste caso el parámetro inicial items es el array de tareas con todas las tareas que le pasaré a la función. y conditions el cual hago un array multivalor para comparar múltiples campos. Ya dentro de la función, recorro los items del array que le paso y hago la comparación en condición, si son distintos retorna false por lo que no lo muestra y si son iguales, o el filtro está igual a '*' (por ello en  la vista, el ion-select la primera option la dejé como todos con el '*' como value) lo permito para que muestre todos.

Ahora solo queda implementarlo en nuestra clase, para ello necesitamos importar la clase del Pipe a nuestro clase pruebafiltros.ts, así:

import { FiltroTareasPipe } from '../../pipes/filtrotareas/filtrotareas';

Ahora debemos indicarle al componente que se usará ese Pipe, por lo que al decorador debemos agregar la linea pipes: [ FiltroTareasPipe ] así:

@Component({
    templateUrl: 'build/pages/pruebafiltro/pruebafiltro.html',
    pipes: [ FiltroTareasPipe ]
})

Ahora, necesitamos obtener el valor de el select en alguna variable para pasarla como condición; podríamos hacer uso de #NombreVariable pero en lo personal y por orden prefiero agregarla al modelo, para ello en nuestra clase pruebafiltro.ts inicializamos una variable del nombre que queramos pero que ese mismo nombre usaremos en la vista, en mi caso la llamaré _filtroTarea así:

...
export class PruebaFiltroPage {
    private tareas: any;
    private usuarios: any;
    private _filtroTarea: any;
...

Y en la vista usando el atributo [(ngModel)] en el select con el valor del nombre de la variable lo agregamos al ion-select y debemos ponerle un nombre (ya que es ley de angular xD) así:

...
<ion-select [(ngModel)]="_filtroTarea" name="filtro_tarea">
    <ion-option selected="selected" value="*">Todos</ion-option>
    <ion-option ngfor="let usu of usuarios" value="{{ usu.id }}">{{ usu.nombre }}</ion-option>
</ion-select>
...

Y ya solo queda hacer uso del Pipe en el ngFor del ion-list, así:


...
<ion-list>
    <ion-item ngfor="let tar of tareas | filtroTareas: {usuario_id: _filtroTarea}">
        <h2>{{ tar.tarea }}</h2>
    </ion-item>
</ion-list>
...

Explico: usamos el carácter del pipe "|" a la variable tareas que es la que contiene todas las tareas, liego le decimos que lo pase por el Pipe que creamos, el cual el nombre debe ser idéntico a como lo llamamos en el decorador @Pipe en la clase filtrotareas.ts y como parámetro después de los dos puntos pasamos la condición (el primer parámetro items se pasa por default que en este caso es la variable tareas el segundo parámetro si se lo debemos pasar que es la condición o condition en nuestra función transform) que en este caso es: de los item de tareas compare el valor del key usuario_id con el valor de _filtroTarea así si ésta es igual a Todos ('*') entonces los mostrará todos, pero si es 1 o 2 mostrará solo las tareas que tengan como valor 1 o 2 en el key usuario_id.

Ya con esto debería correr perfectamente la aplicación y solo sería combinar a como deseen estas funciones, por ejemplo en su tranform podrían enviar más condiciones e incluso cambiar algún valor. Por ejemplo mostrar el nombre del usuario en la tarea tomandolo desde el array de usuarios, etc etc etc...

Los Pipes sirven para mucho más que esto que muestro en ésta mini-guía pero para ejemplo claro y básico me parece que un filtro queda bastante bien, ya lo que su imaginación y trabajo requiera es el limite.

Ya saben que cualquier pregunta en los comentarios es bienvenida.

Saludos kid_goth.

2 comentarios:

  1. buaff
    No entiendo que copiandolo tal cual lo muestras en el ejemplo, de errores

    ResponderEliminar