Pipes (filtros)
Por lo general, se utilizan tuberías (pipes) con interpolación y data-binding para transformar los datos antes de mostrarlos. Angular proporciona algunas pipes integradas para operaciones con cadenas, arrays, fechas, JSON, números, etc...
En el ejemplo de productos vamos a utilizar algunas de ellas ( uppercase → string a mayúsculas, currency → número a moneda, date → fecha tipo Date o string formateada).
Los filtros también pueden recibir parámetros (opciones), que se les pueden pasar concatenados (en orden) usando dos puntos ':'. Por ejemplo, vamos a decirle al filtro de moneda que nuestro precio está en Euros (EUR) y debe mostrar el símbolo de la moneda (€) → 'símbolo' ( más información). También vamos a formatear la fecha en un formato dd/mm/yyyy (más información).
Custom pipes
Además de los pipes definidaos en Angular, podemos crear otros. En realidad es un método que recibe un dato de entrada y una serie de parámetros. Lo que esta función devuelve es lo que al final se muestra en la plantilla.
Para el ejemplo, vamos a crear un campo de texto para que se filtre el array de productos en base a que la descripción contenga el texto escrito. El filtro se hará mediante un pipe que crearemos para ello:
Ahora vamos a crear el pipe que filtrará el array de productos:
ng g pipe pipes/product-filter
Esta es la clase que acabamos de crear:
En este caso la clase utiliza el decorador @Pipe para indicar a Angular el tipo de clase que es. La propiedad name indica el nombre del pipe que tendremos que poner en la plantilla del componente donde lo vayamos a usar.
La clase implementa la interfaz PipeTransform, que nos obliga a su vez a implementar el método transform. El primer parámetro de este método será el dato que tenemos que transformar (lo que hay a la izquierda del caracter '|'), mientras que el resto será los parámetros de entrada del pipe.
Vamos a implementar el método. El primer parámetro del método filter será el array de productos, y el segundo la cadena de búsqueda (string). Devolveremos un nuevo array con los productos filtrados, y si la cadena de búsqueda no está presente, o está vacía, devolvemos el array original.
Para probarlo, iremos al componente products-page, importamos el pipe que acabamos de crear, en el componente, y lo usamos en la plantilla pasándole por parámetro la propiedad search.
Con esto debería funcionar.
Pipes 'puros'
Por defecto, los pipes que creamos en Angular se establecen como 'puros'. Esto implica que para que vuelva a ejecutarse y se actualice la información, debe de cambiar la referencia interna de alguno de los parámetros que se reciben.
Por ejemplo, si se recibe un array u objeto, no se detectarán los cambios internos (añadir o borrar un elemento del array o cambiar el valor de una propiedad del objeto). Para que angular vuelva a ejecutar el pipe, debemos generar un nuevo array u objeto (una copia con las modificaciones pertinentes).
Esto lo hace así por cuestiones de optimizar rendimiento. Es decir, es mucho menos costoso comprobar que la referencia (número) del objeto o colección que se recibe ha cambiado que estar comprobando si se ha cambiado el valor de una propiedad interna o se ha añadido, sustituido o borrado algún elemento de un array (implicar ir recorriéndolo periódicamente).
Se puede cambiar en cualquier momento este comportamiento con la propiedad pure: false en el decorador. Sin embargo, no está aconsejado ya que perderemos rendimiento al aumentar el costo de las comprobaciones periódicas que hará Angular.
En el futuro veremos como en el caso de los componentes el comportamiento por defecto es el contrario, y que si queremos que se comporten como pipes en cuanto a detección de cambios para mejorar rendimiento (perdiendo algo de flexibilidad), debemos configurarlos explícitamente.
Añadiendo o borrando un elemento del array filtrado
En el caso del ejemplo de productos, si estamos filtrando el array de productos e insertamos un producto nuevo, veremos que no se actualiza la lista aunque la descripción del nuevo producto coincida con la cadena de búsqueda. Sin embargo, si modificamos el campo de búsqueda y volvemos a poner el valor anterior, sí se mostrará.
Esto ocurre por lo que acabamos de explicar, añadir un elemento al array (push) no modifica su referencia a memoria (mismo objeto), por lo que Angular no interpreta que algo ha cambiado y no vuelve a ejecutar el filtro. Al cambiar el string de búsqueda, como eso sí genera un nuevo string (diferente referencia), produce la reevaluación del filtro.
Si el campo de búsqueda está vacío, sí que aparece el producto recién insertado. Esto ocurre porque en este caso, el pipe devuelve el array original, y no un nuevo array filtrado, por lo que Angular sí está detectando los cambios en este array original (el que hay en el componente definido). En caso de haber texto, se estaría mostrando un array diferente (el generado por el método filter), y el pipe no lo regeneraría.
Tenemos 2 posibles soluciones:
- Añadir el producto y vaciar el campo de búsqueda forzando la reejecución del pipe, y mostrar todo el array sin filtrar.
- Generar una copia del array original con el nuevo elemento. Esto también fuerza la reejecución del pipe sin dejar de filtrar.
Para implementar la funcionalidad de borrar un producto, podríamos utilizar el método filter para generar un nuevo array con todos los productos menos el que queremos borrar (y que recibimos por parámetro).