Práctica de observables en Angular

  • Por
Primeros pasos trabajando con observables en una aplicación desarrollada con Angular.

En el artículo anterior del Manual de Angular ofrecimos una introducción teórica a todos los conceptos relacionados con los observables. Ahora nos vamos a poner manos a la obra para ver cómo se traduce todo esto en código, mediante un sencillo ejemplo práctico.

En este artículo trabajaremos con los observables, usando la librería RxJS, que es aquella en la que se apoya Angular para introducir este patrón de desarrollo, capaz de aumentar sensiblemente el desempeño de las aplicaciones.

Para esta aplicación práctica vamos a partir de un ejemplo que ya veníamos trabajando anteriormente en este manual. Para que nadie se pierda, dejamos la referencia al ejemplo sobre el que vamos a aplicar los observables: práctica de módulos, componentes y servicios en el sistema de alta y listado de clientes.

En ese pasado artículo pudimos desarrollar dos componentes, que compartían un mismo almacén de datos (un array de objetos cliente). En el componente de alta se generaban nuevos objetos y se introducían en el array y en el componente de listado se mostraban. Nuestro servicio era el encargado de mantener ese almacén de datos y servir de puente de comunicación entre ambos componentes. El ejemplo funcionaba perfectamente y en aplicaciones pequeñas no necesitamos nada más. Pero si tenemos una aplicación grande, o una mediana donde necesitemos mantener un mejor desempeño, aplicar los observables será una opción mucho mejor.

Qué necesitamos para implementar observables

Si leíste con atención el artículo anterior, dedicado a introducir los observables, creo que más o menos puedes tener una idea de lo que vamos a necesitar en este paso de implementación. De todos modos, nunca está de más un pequeño repaso para aclarar las ideas. En resumen, estos son los actores que vamos a usar:

  • Un subject: que es el encargado de generar y emitir los eventos de actualización del almacén de datos.
  • Un observable: que es un objeto que permite observar los eventos emitidos por el subject.
  • Una suscripción: generada a partir del observable.
Nota: Otras implementaciones pueden poner en juego otros tipos de objetos, disponibles en el patrón observer y en la librería RxJS.

El servicio de clientes será el encargado de usar el "subject" para generar los eventos cuando se agreguen nuevos clientes. Este servicio también será el encargado de entregar el observable a los componentes que necesiten escuchar los eventos emitidos por el subject.

Aquellos componentes escuchando eventos son los observadores (observer). Estos observadores declaran y usan el observable y, mediante una suscripción, estarán enterados de cualquier cambio en aquello que se está observando.

Esperamos que estos conceptos estén medianamente claros, puesto que será esencial para poder entender el resto del ejercicio. A continuación podemos comenzar a revisar con detalle el código necesario para realizar todas estas operativas.

Producir el flujo de eventos a observar

El servicio que implementa toda la parte de acceso a los datos, con la lógica de negocio de la aplicación, es el encargado de generar los eventos que los observadores podrán consumir.

El servicio tiene por tanto que conocer la implementación de dos clases:

  • El subject, para generar el stream de eventos
  • El observable, que entregarán bajo demanda a los observadores

Para ello, en el servicio de clientes "clientes.service.ts" vamos a crear los correspondientes imports:

import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';

Como puedes comprobar, estos import no te los ofrece la librería Angular, sino la librería RxJS, que se instaló como dependencia al crear la aplicación Angular.

Declaración del subject como propiedad

El subject será un elemento del servicio y por tanto lo tenemos que declarar como cualquier otra propiedad. Su declaración se realiza mediante el siguiente código.

private clientes$ = new Subject<Cliente[]>();
  • Realizamos la declaración privada, para que nadie pueda acceder al subject, excepto el propio servicio.
  • El nombre de la propiedad privada es clientes$. Es normal que los subjects en el trabajo con observables tengan el carácter "$" al final, para dejar claro lo que son.
  • Usamos un "generic" de TypeScript para indicar el tipo de aquello que vamos a observar. Lo que vamos a observar es el array de clientes, por eso en la instanciación del subject se define el tipo genérico como Cliente[].
  • No hace falta indicar el tipo de la propiedad series$: Subject porque se infiere perfectamente, debido a que la instanciación del subject se hace en la misma línea de la declaración de la propiedad.
Nota: tienes un artículo muy completo para saber más sobre los Generics de TypeScript.

Emitir eventos usando el subject

Ahora, también en el servicio, cada vez que se agregue un nuevo elemento al array de clientes, tenemos que emitir un evento, usando el subject. Esto lo conseguimos, por supuesto, usando el objeto subject de RxJS.

Así quedaría el método que agrega un cliente, que ya tenía nuestro servicio anteriormente. Solo que antes únicamente se realizaba el push() y ahora seguimos realizando la inserción en el array y posteriormente la generación del evento con el subject.

agregarCliente(cliente: Cliente) {
  this.clientes.push(cliente);
  this.clientes$.next(this.clientes);
}

Como puedes ver, el método para crear el evento es "next" y además le tenemos que pasar el estado del array en este momento, para que luego los observadores puedan saber cómo estaba el array al producirse este evento.

Generar el observable

La última acción que vamos a necesitar para completar el trabajo dentro del servicio es la generación del observer, que se entregará a todos aquellos componentes que quieran observar cambios en el almacén de datos.

Es interesante este paso, puesto que el observable es un consumidor de los eventos del subject y es de sólo lectura. Es decir, puede estar atento a eventos, pero es incapaz de hacer nada más. Por tanto, lo único que el servicio entregará a externamente, a cualquier componente que lo necesite, es este observable. Mediante el observable, los componentes sabrán cuando el almacén de datos se ha modificado, pero ningún componente podrá generar nuevos eventos de cambio del almacén de datos, que es una responsabilidad del servicio y tarea principal del subject privado.

El observer se creará mediante un método del subject llamado asObservable(). Usaremos un método "getter" para devolver el observable, que tendrá el siguiente aspecto.

getClientes$(): Observable<Cliente[]> {
  return this.clientes$.asObservable();
}

Como puedes ver, el método devuelve un objeto de la clase Observable, el cual también se tiene que definir especificando, mediante un genérico de TypeScript, aquello que ese observable es capaz de vigilar, en este caso un array de clientes (Clientes[]).

Consumir un observable

Ahora, en cualquier componente que necesitemos estar atentos a los cambios del almacén de datos, podemos usar el observable que nos ofrece el servicio. Para ello necesitamos solamente un par de pasos.

El primero es importar la clase Observable.

import { Observable } from 'rxjs/Observable';

Seguidamente, vamos a crear una propiedad en el componente que permita almacenar el observable. Podemos observar que se declara el tipo, pero no se inicializa todavía.

clientes$: Observable<Cliente[]>;
Nota: De nuevo, para orientar a quien sea que lea este código, le colocamos un $ al final del nombre de la variable, indicando que es un observable.

Crear la suscripción a los eventos

El segundo es crear la suscripción a los eventos que nos entrega el observable, y que fueron generados en el servicio usando el subject.

El lugar adecuado para generar esa suscripción es el método ngOnInit(), que se ejecuta cuando el componente ya se ha inicializado y por tanto tiene todas sus propiedades ya disponibles. Tendrá esta forma:

ngOnInit() {
  this.clientes$ = this.clientesService.getClientes$();
  this.clientes$.subscribe(clientes => this.clientes = clientes);
}
Nota: Así haremos en cualquier otro componente que necesite usar el observable. Obviamente, en el constructor del componente ListadoClientesComponent se debe haber inyectado el servicio que se necesita para pedir el observable.

En el código anterior hacemos dos pasos:

  • Se accede al observable, mediante el método getClientes$() del servicio clientesService. Ese observable es el que puede escuchar los eventos que necesitamos consumir.
  • Se crea una suscripción mediante el método suscribe() del observable. Este método de suscripción debe recibir la función manejadora de eventos que contiene el código a ejecutar cuando se dispara el evento. La función manejadora de eventos recibe el array que se está observando como parámetro.
Nota: Puedes ver que estamos usando una "arrow function" para la definición del manejador de eventos de la suscripción. Podríamos haber usado perfectamente una función anónima como quizás estás acostumbrado, pero aquí es preferible utilizar una "función flecha" por dos motivos. 1) La sintaxis es más concisa y reducida. Pero es más importante aún 2) la función flecha no genera contexto propio, por lo que podemos seguir accediendo a "this". Si lo deseas, en el manual de ES6 puedes obtener mucha más información de las arrow functions.

Básicamente, lo que hace el manejador de eventos de la suscripción es que, cada vez que cambia el array de clientes, actualiza la propiedad interna del componente al nuevo array actualizado, recibido por parámetro.

De este modo, el listado de clientes puede recibir el nuevo array de clientes, cada vez que se actualiza, de una manera optimizada, puesto que no tiene que preguntar todo el tiempo si ese array se ha actualizado, solo sentarse a esperar y ser notificado cada vez que hay cambios.

Después de realizar esas modificaciones, tu aplicación debe seguir funcionando igual que hasta ahora, mostrando clientes en el listado cada vez que el componente de alta de clientes nos genera uno nuevo.

Para resolver posibles dudas en cuanto al código completo del proyecto, tal como lo hemos dejado en este paso, te dejo este enlace a un commit en un repositorio del ejemplo en GitHub. https://github.com/midesweb/ej_facturacion_angular/tree/c240f0de34ab21341c0c85db2b85fabb8c0dab95

En la siguiente entrega, vamos a explicar otra cosa fundamental en este proceso, que debes aprender para completar el flujo: dar de baja las suscripciones a los observables.