Las comunicaciones

En el artículo anterior dejamos preparada la aplicación para poder usar comunicación http con API’s, tanto a nivel global de la aplicación, como a nivel específico del módulo que las va a usar. En este artículo vamos a ver como hacer la conectividad necesaria.

Debemos saber que la conexión de nuestra aplicación con API’s del servidor se puede hacer mediante los métodos POST, GET, PUT y DELETE. En la práctica, sin embargo, emplearemos el método GET cuando el objetivo de la API sea devolverle datos a la aplicación, y POST o GET, cuando el objetivo sea pasarle datos a la API. En la mayor parte de los casos, emplearemos POST, por las ventajas implícitas que presenta: poder enviar paquetes de datos, incluyendo ficheros, poder enviar un volumen de datos mayor, la propia forma de transporte…

En este artículo vamos a empezar a trabajar con el servicio que se ocupa de toda la lógica del CRUD que estamos usando en los últimos ejemplos. Como ahora ya no queremos que los datos queden en memoria, sino que se persistan en una base de datos, tendremos cuatro API’s: una para grabar un nuevo usuario, otra para grabar cambios en un usuario existente, otra para borrar un usuario y una cuarta para obtener la lista de los usuarios.

Vamos a ir paso a paso viendo lo que necesitamos y como montar toda la operativa.

PREPARANDO LA CONECTIVIDAD EN EL SERVICIO

Como todo lo relativo al CRUD que usamos lo tenemos en MembersModule, y toda la lógica de los componentes de ese módulo la tenemos en el servicio members.service.ts, es ahí donde vamos a trabajar. En el artículo anterior ya dejamos importadas las clases HttpClient y Observable, y el fichero de environments. Ahora vamos a preparar las conexiones con las respectivas API’s. Lo primero que hacemos es crear una variable propia del servicio donde almacenaremos la ruta raíz de las API’s:

private URL = environment.API_URL;

Con esto almacenamos la URL base de las API’s en la variable URL. Como dijimos en el artículo anterior, esta puede ser diferente si estamos en desarrollo o en producción, y Angular seleccionará la ruta adecuada en cada caso.

A continuación vamos a preparar el método que permitirá establecer la conectividad con la API destinada a grabar un nuevo registro:

Lo primero que puede llamarnos la atención es que el nombre del método termina con el signo $. Esto no es una necesidad sintáctica, ni obedece a ninguna prescripción de TypeScript o JavaScript. Es un convencionalismo aceptado por todos los desarrolladores que los métodos que establecen una conexión con una API se nombren de esta forma, para reconocerlos con más facilidad.

Vemos que el método va a recibir un objeto de la clase Member como parámetro. Esta se encuentra definida, como ya la teníamos de antes, en la clase member.ts, dentro de members/classes. El argumento lo hemos llamado, por asociación, member.

También debemos fijarnos en el tipo de dato que devuelve. Es de tipo Observable. Los métodos que conectan con una API no devuelven directamente datos, sino que estos están encapsulados en una envolvente de la clase Observable. Además, esta clase debe llevar un modificador de tipo que, en este ejemplo, es <any>. Con eso le estamos diciendo que no sabemos que tipo de dato específico esperamos que nos devuelva la API o que, incluso, podría no devolver nada. Por la API que hemos preparado (ya la veremos), sabemos que devolverá un dato de tipo <string>, así que podríamos haber usado este modificador de tipo. Esto es porque hemos creado la API para que nos devuelva una cadena que nos indicará si se ha podido o no efectuar la grabacción que esperamos que se efectúe en la base de datos. Sin embargo, para no complicarnos en este primer ejemplo, le hemos puesto <any>, que nos vale como «solución universal». No es la mejor práctica pero, en ocasiones, resulta muy cómodo.

Además, de aqui inferimos otra cuestión. Como hemos dicho que el dato no va a ser devuelto de una forma directamente legible, si no encapsulado en una envoltura de clase Observable, este método no podrá ser invocado directamente, sino que se invocará a través de otro método que será capaz de «abrir» esa envoltura y recuperar lo que nos devuelva la API.

Dentro de la clase del servicio tenemos un objeto llamado http. Este existe por el hecho de que lo hemos creado en el constructor, a partir de la clase HttpClient, como vimos en el artículo anterior. Este objeto cuenta, entre otros, con el método post, que es el que vamos a usar para conectar con la API que graba el registro. Este método recibe dos argumentos: el primero es la ruta completa de la API, formada, como vemos, por la ruta base más el nombre del script donde tenemos dicha API. El segundo argumento es el objeto que contiene los datos de un usuario, recibido como hemos visto, en saveRecord$(), con el nombre member. El método post del objeto http envía el dato del segundo argumento a la API indicada en el primero. Además, vemos que retorna «algo» (palabra clave return). Ese «algo» es la respuesta de la API, encapsulada, como hemos dicho, en la envolvente Observable.

LLEGANDO HASTA EL PROCESO DE GRABACIÓN

Acabamos de crear un método que recibirá como argumento un objeto de la clase Member (en los que guardamos nuestros usuarios), lo envía a la API que deberá persistirlo en la base de datos, y nos devuelve un Observable con la respuesta que indica si se ha podido grabar o no. Ahora vamos a ver el resto del proceso que ocurre desde que el usuario pulsa en el formulario el botón para grabar los datos de un nuevo miembro, hasta que este es grabado (o no) en la base de datos.

Cuando el usuario pulsa el botón de grabar, se supone que ha tecleado correctamente el DNI y el nombre del nuevo miembro. Pero «se supone» no nos vale. El usuario ha podido equivocarse y dejar en blanco alguno de los campos. Y no queremos eso. Así que, cuando el usuario pulsa el botón de grabación, se dispara el método checkRecord():

Este método es muy simple. Luego lo complicaremos un poco más, para darle más funcionalidad pero, por ahora, nos vale así. Lo que hacemos es comprobar si las propiedades dni y nombre de la clase en la que estamos trabajando (la del servicio), tienen contenido o son cadenas vacias. Si ambas tienen un contenido, creamos un objeto llamado User, de la clase Member), con los datos que nos acaban de llegar. Luego llamamos al método saveRecord() que será el que, a su vez, llame al metodo saveRecord$() que hemos creado anteriormente. De esta forma lo tenemos todo muy desacoplado, como veremos en seguida.

El método saveRecord() es el siguiente:

Como ves, este método es el encargado de mandarle el objeto User al método que, a su vez, conectará con la API. Además, a esta llamada le encadenamos el método subscribe(). Este último es el que recupera el Observable, y lo «destripa» para sacar la respuesta. Si la respuesta (tanto si se ha podido persistir el registro en la base de datos como si no) es sintácticamente correcta, se ejecuta el método saveSuccess(). En caso de que se haya producido un error de ejecución en la API se ejecuta el método catchError(). Como ves, ambos métodos aparecen como argumentos de subscribe(). Podría haber un tercer argumento que se refiriese a un método que se ejecutaría, al finalizar, en cualquier caso. Esta operativa recuerda a las promesas de ES6, o al funcionamiento del $.ajax() de jQuery.

El método saveSuccess() se ejecuta, como decíamos, si la API ha funcionado sin errorres, tanto si el registro se ha podido persistir, como si no. La razón más común para que un registro no pueda persistirse es que no cumpla alguno de los requisitos de la base de datos. En este caso, la hemos diseñado de tal modo que el campo DNI debe ser único, es decir, que no puede haber dos o más socios con el mismo DNI. Si el DNI enviado ya existe, capturamos la excepción y mandamos su código a members.service.ts, para que reaccione en consecuencia (por ejemplo, informando al usuario). El método queda así:

En este ejemplo verificamos el código 23000, por ser el que devuelve PDO cuando se intenta grabar un registro con un campo repetido, si este campo está definido como único en la base de datos.

En caso de que en la API se haya producido algún error de ejecución o una excepción no capturada se activará el métod catchError():

Por lo tanto, el esquema operativo queda como se ve a continuación:

CONSIDERACIONES IMPORTANTES

Hemos visto, a grandes rasgos, el proceso que llega desde la interfaz del usuario al acceso a una API, de forma exitosa o no. Una cuestión que se suscita en este punto a menudo es el nombre de los métodos. Puedes llamar a tus métodos como desees, pero se recomienda seguir ciertas pautas, para facilitar la legibilidad y mantenibilidad del código. Son muy básicas y fáciles de recordar:

  • En primer lugar, a los métodos que actúen sobre el objeto de la clase HttpClient devolviendo un Observable los nombraremos finalizando el nombre con $. Como hemos dicho, esto no es sintácticamente prescriptivo, pero si muy útil desde el punto de vista semático y organizativo.
  • Los métodos que invocan a los anteriores para recuperar el Observable se llamarán con el mismo nombre, sin finalizar con $. Lo hemos visto en el ejemplo de este artículo, en el que el método saveRecord() invoca a saveRecord$().
  • Para evitar confusiones, aclarar que el método encargado de «abrir» el Observable se llama subscribe() porque así está definido en la sintaxis de Angular. Al contrario que el resto de los métodos que hemos usado en el ejemplo, el nombre de este método no ha sido elegido arbitrariamente por el programador. Puede paracer una obviedad, pero mucha gente, al principo, se induce a confusión, pensando que este método puede llamarse como deseemos mientras esté «en su sitio». No es así.

ACERCA DEL MÉTODO subscribe()

El método subscribe() constituye uno de los pilares fundamentales de las comunicaciones con API’s. Su misión es desenvolver lo que le llega en la envoltura Observable y, según lo que encuentre, ejecutar un método u otro. Su sintaxis general admite hasta tres parámetros, como ya hemos mencionado anteriormente:

  • El primero, es el método que se ejecutará si el resultado de la API se ha podido obtener sin errores sintácticos o de ejecución. Puede recibir el valor null, pero no tendría ningún sentido lógico.
  • El segundo parámetro es una «salida» que le proporcionamos para que se ejecute un método en caso de que durante la ejecución de la API se haya producido un error sintáctico. Ese hecho, en otras condiciones, abortaría la ejecución de nuestra aplicación Angular. Es como capturar una excepción y darle una «salida honrosa». Puede recibir el valor null o, incluso, no existir, pero no es una práctica aconsejable. Lo suyo es ponerlo.
  • El último parámetro sería un método que se ejecutaría al finalizar, tanto en un caso como en otro. No siempre es necesario y, de hecho, como ves, de momento no lo hemos usado.

Por lo tanto, el conjunto de los tres parámetros forman una operativa similar al uso de las estructuras try...catch...finally, salvando las diferencias, dentro del contexto de la sintaxis de Angular.

Observa la forma en la que se invocan los métodos que constituyen los parámetros de subscribe(). Por ejemplo, para llamar al método saveSuccess() lo invocamos así:

this.saveSuccess.bind(this)

La primera aparición de la partícula this se refiere a la propia clase desde la que estamos trabajando, es decir, en este ejemplo, MembersService. El nombre saveSuccess es el del método que desamos invocar. Esto, por lo tanto, sólo invoca al método. Sería el equivalente a haber escrito this.saveSuccess(). Sin embargo, vemos que encadenamos el método bind(this). Eso hace que el cuerpo de saveSuccess() actúe sobre el objeto que se pasa como argumento. Gracias a eso, podemos obtener el resultado de haber abierto el Observable en el parámetro result.

Y una cosa muy importante acerca del método subscribe(). Siempre debe ir encadenado al método que llama al que actúa sobre la API. Por ejemplo, podrías pensar que el método saveRecord() podríamos haberlo escrito así:

Sintácticamente es correcto e, incluso, parece más claro y legible. Sin embargo, esto presenta un problema insoluble. Como la llamada a saveRecord$() es asíncrona, lo que ocurrirá es que el método subscribe() se invocará antes de que tengamos el Observable a nuestra disposición. Por lo tanto, los resultados no serán nunca los que esperamos. Por esta razón, encadenamos subscribe() al final de saveRecord$() como hemos hecho antes. Así nos aseguramos de que no se ejecute hasta que tengamos el Observable.

CONCLUYENDO

En este artículo nos hemos asomado a la forma de llamar a una API desde nuestra aplicación Angular, enviarle unos datos, y escuchar su respuesta. Esta vez no te pongo el código para descargar, porque todavía nos faltan cosas que aprender sobre este tema. En el artículo que proceda te pondré todo el código para que puedas examinarlo paso a paso siguiendo el texto. Paciencia. A veces, antes de pasar a la práctica, es necesario un poquito de teoría. Nos vemos en el siguiente artículo.

   

Deja un comentario