Última actualización: 22 noviembre, 2020
El otro día en el trabajo tuve que construir un filtro para filtrar (valga la redundancia) datos de una tabla y hacer que se muestren sólo los seleccionados. Algo así como el filtro que utiliza JIRA:
Aunque en la documentación de Angular Material encontré cosas útiles, evidentemente no iba a encontrar una solución a medida. Así que la tuve que construir. Aquí te enseño cómo. ¡Vamos allá!
Qué vamos a construir
En este post vamos a construir los filtros desde el punto de vista de la UI. Es decir, nos vamos a centrar en la parte que viene antes de la integración con la tabla, no en la integración en sí.
Vamos a hacer dos filtros lo más parecidos a los de JIRA que ves arriba, usando Angular Material. Para hacer este ejemplo lo más sencillo posible, imaginemos que tenemos una tabla con información que recoge todas las películas que has visto en tu vida. Y queremos diseñar filtros para filtrar por:
- actor principal
- un rango de presupuesto que costó hacer la peli (es decir, entre XXX € y XXX €)
Cómo vamos a contruirlo
Nuestros filtros se van a basar en dos componentes de Angular Material: el expansion panel y la list. Vamos utilizar esos componentes de base y combinarlos con los siguientes aspectos:
- clases de CSS dinámicas (ngClass) en conjunto con el tertiary operator y el or operator ( || )
- local references para acceder a partes de nuestra template HTML
- ngFor para generar una lista con datos ficticios (en inglés, dummy data)
- ngIf para ocultar partes del DOM cuando nos convenga
- ngModel para vincular nuestros datos (en inglés, data binding)
Construcción del filtro del actor principal
1. En tu proyecto de Angular ya arrancado, instalamos Angular Material y lanzamos el servidor desde la terminal con ng serve -o.
Vamos a empezar con una de las estructuras modelo que nos da Angular Material en la sección del Expansion Panel. Trabajaremos directamente en el app.component.html.
El selector mat-expansion-panel viene con una serie de propiedades, como opened y closed, que son las que vamos a usar. Estas propiedades son eventos que se disparan cada vez que el expansion panel se abre y cierra, respectivamente.
Angular Material nos proporciona esos eventos para que podamos reaccionar cada vez que el expansion panel se abre (evento opened) o se cierra (evento closed). Aquí está una de las claves. Pues al tener acceso a esos eventos, podemos configurar lo que queramos. Por ejemplo, podemos decirle al panel: "Cada vez que te cierres, comprueba si alguien ha añadido algo dentro del expansion panel, y de ser así, cambia de color". Veremos cómo hacer esto más adelante.
2. Importamos el MatExpansionModule y el MatSelectionList en el app.module.ts.
import { MatExpansionModule } from '@angular/material/expansion'; import { MatListModule } from '@angular/material/list'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, BrowserAnimationsModule, MatExpansionModule, MatListModule
Si ves que la terminal te da errores, es normal, pero ya deberías ver al menos el expansion panel en tu navegador. De no ser así, cancela el local server escribiendo ctrl+c en tu terminal y vuelve a lanzarlo. A mí me pasa a menudo y al principio me obsesionaba pensado qué podía estar fallando. Ahora no me complico. Ctrl+c cuando antes y problema resuelto.
3. Creamos la propiedad actorPanelOpenState en el app.component.ts.
export class AppComponent { actorPanelOpenState = false; }
Su valor es false porque el estado inicial del panel es siempre cerrado (por lógica, aunque tú puedes necesitar que nada más mostrarse el panel, se muestre directamente abierto. Pero yo lo voy a dejar así).
4. Nos deshacemos del texto entre el mat-panel-description porque no lo vamos a necesitar.
5. En el app.component.css, creamos clases que posteriormente aplicaremos de manera dinámica. Vamos a crear clases de diferente anchura para mostrar el filtro cuando esté vacío y sólo se lea algo tipo:
y para cuando el filtro esté activo y haya algún elemento añadido (algún actor), tipo:
Estas anchuras cumplen el objetivo:
.no-filter-width { width: 12vw; } .active-filter-width { width: 20vw; }
6. Creamos un array con nombres de actores en el app.component.ts, que constituirá nuestra dummy data.
actors: string[] = ['Bradley Cooper', 'Jennifer Lawrence', 'Penelope Cruz', 'Javier Bardem', 'Winona Ryder'];
7. Justo después del </mat-expansion-panel-header> del template, añadimos la lista de actores, utilizando un ngFor para mostrarla. Ya podemos deshacernos del <p> que venía con la plantilla y cambiarle el título al mat-panel-title.
Si guardas y vas a tu navegador, deberías ver un expansion panel con una lista de actores que se despliega al hacer clic. ¡Chachi
Cómo cambiar el panel de color según haya algún filtro seleccionado
En el app.component.css añadimos unas clases que coloreen el texto de blanco y el fondo de verde cuando un filtro contenga algún actor seleccionado.
.active-filter-bg { background-color: teal !important; } .active-filter-text { color: white; }
Para averiguar cuándo una opción de la lista está seleccionada, plantamos una "bandera" en el mat-selection-list usando local references (en el app.component.html). La llamamos actorsList. Eso nos abre un maravilloso mundo de posibilidades. Por ejemplo, ahora podemos acceder a propiedades como selectedOptions. Como su nombre indica, es una propiedad que se aplica cuando hay alguna opción de la lista seleccionada.
Esta propiedad nos ofrece otras como la propiedad selected, que identifica la opción que marquemos de la lista. Yendo un paso más allá, tenemos la propiedad length, que nos dice cuántas opciones de la lista hemos marcado. Para entender todo esto, creamos un <p> debajo de la mat-selection-list y aplicamos todas estas propiedades a nuestra local reference.
¡Y ahí lo tenemos! Al final de la lista, cada vez que haces clic en un actor, el contador se activa, mostrando cuántos actores has seleccionado. Esto nos es tremendamente útil, porque, por ejemplo, podemos configurar el código para que compruebe si hay algún actor seleccionado, y de ser así, aplicar las clases active-filter-width, active-filter-bg y active-filter-text.
Para eso utilizamos clases dinámicas con el ngClass, tanto en su versión normal como con el tertiary operator, algo que aprendí en profundidad en este curso de Angular.
¡Y listo! Ahora si haces clic en el panel y seleccionas alguna opción, verás que el panel cambia de color siempre y cuando haya como mínimo una opción seleccionada. Eso es lo que hemos configurado al hacer que, de la lista de actores, compruebe si hay alguno seleccionado (es decir, si la length es mayor que 0). Espero que se entienda mi traducción de "código" a "humano"
Cómo mostrar el filtro seleccionado
Vamos a añadirle el toque final a nuestro filtro, haciendo que se vea la opción seleccionada a continuación del título del filtro. De ser demasiadas las opciones, mostraremos unos puntos suspensivos y en cualquier caso, mostraremos la cantidad de opciones (actores) que hemos seleccionado.
1. Para eso, usamos una propiedad que viene por defecto: value. La añadimos al mat-list-option y le damos el valor de la variable local actor. Con esta pequeña magia, ahora podemos obtener y mostrar el actor/es seleccionado/s.
Podemos hacer esto porque la variable local actor muestra, usando un loop, todos los elementos de un array. Por tanto, podemos acceder a ellos usando su index. Por ejemplo, si queremos mostrar los dos primeros, usamos los index [0] y [1]. Vamos a mostrarlos en el mat-panel-description.
2. Añadimos dos puntos cuando haya algún actor seleccionado, arreglamos la coma para que sólo se vea cuando haya más de un actor seleccionado y acortamos el texto para que salgan puntos suspensivos.
.text-truncate { width: 9vw; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
3. Echamos mano del contador que habíamos usado arriba, esta vez para añadirlo a continuación de los actores seleccionados para saber cuántos actores hemos seleccionado. Para eso, usamos la misma propiedad que antes (length), pero pidiéndole que nos muestre los valores seleccionados a partir de los nombres que ya vemos.
O sea, como máximo vamos a mostrar dos nombres y a partir del tercero ya mostraríamos el contador, que empezaría a contar desde +1.
Lo añadimos al final del mat-panel-description.
4. Nos deshacemos del contador que habíamos utilizado para pruebas (el <p>).
¡Y voilà, nuestro filtro está listo!
Construcción del filtro del rango de presupuesto
Vamos a seguir algunos de los patrones del filtro anterior y añadir cosas nuevas. Lo más destacable es que para este filtro vamos a usar la directiva ngModel, directiva que estudié en profundad en este curso de Angular.
1. Necesitaremos igualmente un mat-expansion-panel, con las propiedades opened y closed, vinculadas a una nueva propiedad: budgetPanelOpenState. La propiedad budgetPanelOpenState tiene el mismo objetivo que en el filtro del actor principal, esto es, establecer el estado inicial del panel, y, combinada con las propiedades opened y closed, reaccionar cuando el panel se abra o se cierre.
2. Creamos la propiedad budgetPanelOpenState en el app.component.ts, y le asignamos el valor de false.
3. Creamos también dos propiedades más: minAmount y maxAmount. Nos servirán para establecer el rango de presupuesto que ha costado una película, con el objetivo de poder filtrar películas que hayan costado entre 1 millón y 10 millones de €, por ejemplo.
budgetPanelOpenState = false; minAmount: number; maxAmount: number;
4. Importamos los módulos FormsModule, MatInputModule y MatIconModule en el app.module.ts.
import { MatInputModule } from '@angular/material/input'; import { MatIconModule } from '@angular/material/icon'; import { FormsModule } from '@angular/forms'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, BrowserAnimationsModule, MatExpansionModule, MatListModule, MatInputModule, MatIconModule, FormsModule
5. Debajo del mat-expansion-header, creamos una sección donde irán dos inputs (el de minAmount y el de maxAmount).
6. Utilizamos el ngModel para vincular las propiedades minAmount y maxAmount que hemos creado en el archivo TS. Las usamos sobre los inputs.
7. Mostramos el contenido de los inputs (cuando exista) en el mat-panel-title.
8. Utilizamos un tertiary operator para comprobar si el panel está abierto o si el usuario ha escrito alguna cantidad. De ser así, aplicamos la anchura adecuada.
9. Por último, aplicamos un color cuando haya alguna cantidad añadida, para destacar que el filtro está activo.
¡Y ya lo tenemos! Ahora un poquito de chapa y pintura...
.budget-range { display: flex; justify-content: space-between; margin-top: 20px; } .budget-range mat-form-field { width: 45%; }
¡Y tacháááán!
THE END!
¡Y hasta aquí el post de hoy! Espero que hayas aprendido algo nuevo
Sobre la autora de este post
Soy Rocío, una abogada reconvertida en programadora. Soy una apasionada de aprender cosas nuevas y ferviente defensora de que la única manera de ser feliz es alcanzando un equilibrio entre lo que te encanta hacer y lo que te saque de pobre. Mi historia completa, aquí.
Más recursos de aprendizaje
En mi experiencia, la manera más eficaz para aprender Angular es combinando varias vías de aprendizaje. Uno de mis métodos favoritos son los vídeo-cursos y mi plataforma predilecta para eso es Udemy. He hecho varios cursos pero sólo recomiendo aquellos que verdaderamente me han sido útiles. Aquí van:
Si necesitas apoyo en forma de libro, puede que éstos te sirvan de ayuda:
La programación es un mundo que evoluciona a una velocidad de vértigo. Los autores de estos libros lo saben, por eso suelen encargarse de actualizar su contenido regularmente. Asegúrate de que así sea antes de adquirirlos
Participo en el programa de afiliados de Udemy y Amazon, lo que significa que, si compras alguno de estos cursos y/o libros, yo me llevaré una pequeña comisión y a ti no costará nada extra. Vamos, lo que se dice un win-win
Otros artículos que pueden interesarte