En el artículo anterior aprendimos a integrar DataTables en nuestro proyecto Angular 6. Sin embargo, lo que hicimos nos quedó imcompleto. En este artículo vamos a ampliarlo, añadiendo, para los administradores de nivel full, la posibilidad de crear los botones de edición y borrado de un socio. El botón de crear un nuevo miembro lo dejaremos para más adelante.
Con esto, nos quedará el CRUD de socios casi completo, tal como lo teníamos ya, pero usando DataTables, lo que siempre proporciona mejores resultados finales.
Aprenderemos a leer columnas que no se van a mostrar tal cual, sino adaptadas a nuestras necesidades. Ya hicimos un tosco acercamiento a esto cuando mostrábamos las fechas en formato español (sabemos que, en la base de datos están en formato ISO 8601 extendido). En este artículo vamos a dar un paso más creando elementos con los datos recibidos.
LO QUE TENEMOS QUE HACER
En este artículo vamos a hacer varias cosas interesantes:
- En primer lugar, si el administrador logueado tiene el rol
ROLE_FULL
, le mostraremos los botones de edición y borrado. Esto es más sencillo de lo que parece. - Si se pulsa el botón de edición, se pasará al componente adecuado, para editar la ficha del socio. Esta parte no tiene nada nuevo respecto a como lo habíamos hecho anteriormente.
- Si se pulsa el botón de borrado, se mostrará el modal para pedir confirmación. También funciona como ya conocemos.
- Por último, si se borra un socio, se debe redibujar la tabla, para que el miembro borrado ya no aparezca. Esto, que en proyectos no Angular se resolvía con una simple instrucción, en un proyecto Angular es más complejo, y requiere varias novedades. Esta será, sin duda, la parte más ardua del artículo, pero es algo que debemos conocer para nuestros proyectos. Además, una vez aprendido el mecanismo, para otros proyectos es tan simple como copiar y pegar, cambiando, únicamente, los nombres de los elementos interesados.
LOS BOTONES DE EDICIÓN Y BORRADO
Lo primero que tenemos que hacer, en la lógica de MembersListComponent
, es recuperar el id
de cada miembro. Eso no es problema, porque la API ya nos proporciona todos los datos de cada registro leido, así que, en realidad, ya lo estábamos haciendo, aunque no mostrásemos ese campo en la lista de columnas.
En la vista, si tenemos el rol de admin de mayor rango, sí tenemos que mostrar dos columnas más en la tabla, para los botones. La cabecera de la tabla la recrearemos así:
1 2 3 4 5 6 7 8 9 10 11 |
... <thead> <tr> <th width="140">DOI</th> <th>Nombre</th> <th width="100">F. Ingreso</th> <th *ngIf="rolAdmin == 'ROLE_FULL'" width="40">ED.</th> <th *ngIf="rolAdmin == 'ROLE_FULL'" width="40">DEL.</th> </tr> </thead> ... |
Como ves, las dos últimas casillas están supeditadas a que el administrador logueado sea del rol ROLE_FULL
. Además, si aparecen estas celdas en la cabecera, debemos crearlas, también, en las filas, así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<tbody> <tr *ngFor="let member of Members"> <td>{{ member.doi }}</td> <td>{{ member.nombre }}</td> <td>{{ member.fecha_de_ingreso }}</td> <td *ngIf="rolAdmin == 'ROLE_FULL'" align="center"> <a routerLink="form/{{ member.id }}"> <span class="btn btn-warning"> <i class="icon ion-md-create"></i> </span> </a> </td> <td *ngIf="rolAdmin == 'ROLE_FULL'" align="center"> <span class="btn btn-danger" (click)="preavisoDeBorrado(member)"> <i class="icon ion-md-trash"></i> </span> </td> </tr> </tbody> |
Observa las líneas resaltadas. Hemos construido, al igual que hacíamos antes, los botones con datos que obtenemos de la lógica del componente.
El funcionamiento de estos botones es el mismo que ya conocemos. Si se pulsa el botón de edición, el enrutador del módulo nos conduce al formulario que nos permite editar la ficha del miembro. Si se pulsa el botón de borrado, se activa un modal para pedir confirmación. No hay ninguna novedad en esto.
RECONSTRUIR LA TABLA TRAS UN BORRADO
Esta parte si es más peliaguda. Para recrear una tabla de DT hay que contar con un trigger, esto es, un disparador que lance la desctrucción de la tabla actual, y su recarga. Este tiene que estar bindeado a la propiedad dtTrigger
de la tabla en la vista. Esta propiedad es la que permite que, cuando se dispara el trigger en la lógica, se actualice la vista. Lo vemos en la declaración de la tabla:
1 2 3 4 5 |
<table datatable [dtOptions]="dtOptions" [dtTrigger]="dtTrigger" class="table table-striped table-bordered row-border hover" width="100%"> |
En la vista ya no tenemos más de que ocuparnos. Bueno, sí. Cuando descargues la aplicación, verás que están los avisos de «Operación correcta» o «Error en el proceso», como ya teníamos, pero estos funcionan como siempre, mediante variables lógicas, y no hay nada nuevo en ellos. Con el HTML ya hemos terminado.
En la lógica es donde la cosa es más engorrosa. En primer lugar, tenemos que empezar haciendo más importaciones. Desde el nucleo de Angular (@angular/core
) nos hemos traido AfterViewInit
, OnDestroy
y ViewChild
. Las dos primeras son interfaces que vamos a necesitar para el proceso. ViewChild
define un tipo de decorador que usaremos para cargar la declaración de una de las clases propias de DataTables.
La clase que vamos a cargar mediante el decorador ViewChild
es DataTableDirective
que, internamente, maneja algunas operativas de DT. También tenemos que importarla en nuestro componente.
Además, debemos importar la clase Subject
, de rxjs
. Como sabemos, esta es una librería gratuíta, de uso libre, desarrollada por Microsoft. La clase Subject
es una forma particular de observable, que nos permite encapsular el tipo DataTableDirective
para gestionarlo.
Las importaciones, por lo tanto, nos quedan así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { Component, OnInit, AfterViewInit, OnDestroy, ViewChild } from '@angular/core'; import { Router } from '@angular/router'; /* Dos importaciones necesarias para poder crear una instancia de la tabla y manejarla como un objeto Subject */ import { DataTableDirective } from 'angular-datatables'; import { Subject } from 'rxjs'; /* Importamos la clase Member que necesitaremos para gestionar los objetos de los usuarios. */ import { Member } from '../classes/member'; import { DataTablesResponse } from '../classes/data-tables-response'; import { CheckAdminService } from '../../services/check-admin.service'; import { HttpConnectService } from '../services/http-connect.service'; import { HttpErrorResponse } from '@angular/common/http'; |
En las líneas resaltadas ves las importaciones nuevas que hemos añadido.
Lo siguiente es implementar las interfaces AfterViewInit
y OnDestroy
en la declaración de la clase de nuestro componente, así:
export class MembersListComponent implements AfterViewInit, OnDestroy, OnInit {
Como ya sabes, eso significa que tendremos que sobrescribir los métodos ngAfterViewInit()
y ngOnDestroy()
, declarados en estas interfaces. Ya llegaremos a eso.
Ahora tenemos que usar el decorador de ViewChild
para recibir la clase DataTableDirective
. A partir de ahí crearemos un elemento (al que, en este ejemplo, vamos a llamar dtElement
) para poder gestionar la tabla, y un trigger (al que vamos a llamar, en este ejemplo, dtTrigger
), para poder disparar la reconstrucción de la tabla cuando lo necesitemos:
1 2 3 4 5 |
/* El decorador @ViewChild recibe la clase DataTableDirective, para luego poder crear el dtElement que represente la tabla que estamos creando. */ @ViewChild(DataTableDirective) dtElement: DataTableDirective; dtTrigger: Subject<DataTableDirective> = new Subject(); // La clase Subject es un genérico, tenemos que atribuirle el tipo adecuado. |
Ahora, al final del código (esto es por cuestiones organizativas, para que nos quede ordenado, no por causas operativas) redefinimos los métodos que hemos implementado a partir de las interfaces, y creamos un método, al que vamos a llamar reDraw()
, que será, realmente, el encargado de redibujar la tabla cuando llegue el momento. Nos queda así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
ngAfterViewInit(): void { this.dtTrigger.next(); } ngOnDestroy(): void { // Hay que dessuscribirse del evento dtTrigger, para poder recrear la tabla. this.dtTrigger.unsubscribe(); } reDraw(): void { this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { // Destruimos la tabla dtInstance.destroy(); // dtTrigger la reconstruye this.dtTrigger.next(); }); } |
El método reDraw()
es invocado cuando el borrado de un elemento ha finalizado adecuadamente. Veamos que hace:
- En primer lugar crea una instancia del elemento que representa a la tabla. La instancia la hemos llamado
dtInstance
y, como ves, la sintaxis es un poco arcana, ya que usa una promesa de JS para crearla, siendo del tipoDataTables.Api
. Esto es parte del código del DataTables para Angular. No vamos a detenernos en como funciona «por dentro», porque excede de lo que pretendemos aquí. - Después, destruye esa instancia con el método
destroy()
. Como la instancia está apuntando a la tabla, realmente se está destruyendo la tabla. Es decir. La instancia no es una copia de la tabla. Es un puntero a la tabla. - Cuando se produce la destrucción, el método
ngOnDestroy()
se ocupa de «aislar» el trigger de la tabla que hemos destruido, mediante el uso del métodounsubscribe()
. - Por último, el méto
next()
del trigger recrea la tabla, con lo que se vuelven a leer los datos que ahora quedan. Al recrearse la tabla, el trigger vuelve a quedar «suscrito» al observable de claseSubject
, por lo que estará disponible para el próximo borrado.
El proceso no es fácil de entender, eso es cierto. Implica el uso de clases y elementos del DataTables de Angular, de la librería rxjs
, que requeriría un libro entero sólo dedicado a ella, y muchos conceptos no habituales. Desde luego, como intentes entenderlo paso a paso, te aseguro que te vas a tirar muchas semanas buceando por foros y blogs. Sin embargo, seamos claros. No merece la pena ese esfuerzo y tiempo. Basta con tener el código a mano, y copiar y pegar cuando haga falta. Lo único que tienes que tener en cuenta es que los nombres del elemento DataTables, del trigger, de la instancia, etc coincidan en donde hagan falta, para evitar errores.
CONCLUYENDO
Este artículo termina aquí. Como siempre, la aplicación, en su estado actual, puedes descargarla en este enlace. En el próximo artículo añadiremos el botón de crear nuevo usuario, y alguna cosa más, para mejorar nuesta lista de miembros.