¿Quién no ha comenzado a trabajar con Angular, se ha encontrado con los subjects y no ha sabido qué eran o cómo funcionaban?

En este artículo vamos a hablar un poco sobre este observable y las diferentes variantes que existen. Pero empezar hablando directamente sobre subjects tendría poco sentido si primero no hacemos una introducción sobre los observables y los problemas que soluciona el tema central de este documento.

¿Qué es un observable?

Un observable es un stream o fuente de datos que puede llegar en cualquier momento y estamos interesados en observar.

¿Qué es un observer?

Un observer es una colección de callbacks que “sabe” escuchar los valores o eventos que el observable emite.

Hasta acá todo parece muy habitual, pero uno de los problemas de entender los observables es que pueden aparecer situaciones donde pensamos que dos o más observers recibirán el mismo valor y sin embargo esto no sucede.

¿Por qué puede pasar algo así? La respuesta es que existen observables calientes y observables fríos.

Un observable caliente se caracteriza por emitir valores sin necesidad de que haya alguna suscripción activa, ya que la información del stream se produce fuera del propio observable. Un observable frío, en cambio, se caracteriza por emitir valores únicamente si existe al menos una suscripción activa, ya que la información es producida dentro del observable. Por lo tanto, solo emite valores cuando se produce una nueva suscripción.

Acá es cuando aparecen los subjects para salvarnos de este problema. Si queremos compartir el mismo stream con todos los suscriptores sin preocuparnos si el observable es frío o caliente, estos son nuestra solución.

Subjects

Un subject es, por raro que parezca, un observer y un observable al mismo tiempo. Uno puede insertarle nuevos valores, como también suscribirse a ellos. Una especie de “Read & Write”.

Para que quede más claro, lo podemos analizar desde los dos puntos de vista:

  • Subject como observable: dado un subject, es posible suscribirse a él proporcionando un observer que comenzará a recibir valores. En su funcionamiento interno, el "subscribe" simplemente registra al observer en una lista de observers que "escuchan" valores del subject en cuestión.
  • Subject como observer: es un objeto con los métodos ya conocidos: next(valor), error(e) y complete(). Si queremos alimentar al Subject con un nuevo valor, lo único que debemos hacer es realizar un next(valor a cargar) y se enviará a los observers que estén suscritos. Todos recibirán el mismo valor, por eso se dice que trabaja en multicast.

¿Qué queremos decir con que subject trabaja en modo multicast?

Subject es que es un tipo muy especial de observable, ya que permite que los valores sean “multicasted” a más de un observer. A diferencia de los observables simples que son unicast, todos los observers suscritos a él recibirán los mismos valores.


Diferentes tipos de subject

Si bien pueden ser creados simplemente con la clase subject, existen distintos tipos de subject que ofrecen características diferentes según la necesidad que tengamos. Estos son el BehaviorSubject, ReplaySubject y AsyncSubject.

BehaviorSubject

Como sucede en todos los subjects, quien esté suscrito comenzará a recibir la información luego de realizar el subscribe. Lo característica especial que tiene este tipo de subject es que permite “recordar” el ultimo dato que se emitió en su buffer. Por ende, si el suscriptor realiza la suscripción luego de que se haya realizado el next(valor), igualmente podrá obtener ese valor emitido. Es de gran utilidad si queremos mantener un sincronismo entre todas las suscripciones.

Además, otra de las particularidades que posee el BehaviorSubject es que al momento de inicializarlo debemos sí o sí otorgarle un valor, en cambio en el subject esto no es necesario.

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d791379e-0750-4737-9fd1-c22be2447226/Untitled.png
Como vemos en la figura, el BehaviorSubject se crea con el valor rosa. Cuando el primer observer se suscribe recibe el valor rosa, ya que es el ultimo valor que tiene guardado. Luego sigue recibiendo todos los valores que van llegando. Cuando el segundo observer se suscribe recibe el valor verde (último que quedó guardado) y luego seguirá recibiendo los valores que lleguen.

En caso de que suceda un error, el BehaviorSubject no emitirá ningún valor, pero comunicará el error sucedido.

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/03a24cb8-5edb-4380-a74f-c2e13896c782/Untitled.png

ReplaySubject

Su funcionamiento es similar al BehaviorSubject, con la modificación de que puede guardar más de un valor en el buffer y comunicarlo si se suscriben luego de haber emitido esos valores. La cantidad de valores que puede guardar será indicada al momento de crearlo.

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/35791057-4d88-4d96-af7c-ba6523cfb5d5/Untitled.png
En este caso, la cantidad de valores que puede guardar el ReplaySubject es como mínimo dos, ya que vemos que al realizar la suscripción, el segundo observer recibe los dos últimos valores emitidos.

AsyncSubject

Es una variación al subject clásico que se utiliza para determinados casos específicos. Su diferencia es que el momento donde emite el valor que posee es cuando el observable ha finalizado, es decir, cuando emitió su complete().

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/786c35f1-5bb2-4785-a2a3-0825375fe82b/Untitled.png

Para terminar de entender estos conceptos, vayamos un poco al código y analicemos algunos ejemplos:

Para comenzar debemos importar las tres clases: BehaviorSubject, ReplaySubject y AsyncSubject, provenientes de RxJs.

import { BehaviorSubject, ReplaySubject, AsyncSubject } from 'rxjs';

Comencemos analizando el BehaviorSubject:

behaviorSubject = new BehaviorSubject(0);

Lo inicializamos con el 0, por ende, cuando se suscriban a él deberían recibir ese número.

Realizamos dos suscripciones y cambiamos el valor 3 veces.

// Primera Suscripción
this.behaviorSubject.subscribe(data => {
	console.log('Primera Suscripción:', data);
});

this.behaviorSubject.next(1);
this.behaviorSubject.next(2);

// Segunda Suscripción
this.behaviorSubject.subscribe(data => {
	console.log('Segunda Suscripción:', data);
});

this.behaviorSubject.next(3);

Verificamos en la consola y vemos lo siguiente:

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/12c13f3e-b180-442f-b3df-50747c04e02a/Captura_de_pantalla_de_2020-02-17_09-28-10.png

Algunas cosas para destacar:

  • Al realizarse la primera suscripción el observer obtiene el número 0, una de las características que antes mencionamos de este tipo de Subject. Lo mismo sucede con la segunda suscripción, que obtiene el número 2.
  • Vemos como luego de cada next(valor), cada observer obtiene el mismo valor.
  • Podemos ver lo mencionado previamente: los subjects funcionan de observables y observers.

ReplaySubject:

replaySubject = new ReplaySubject(3);

Inicializamos el ReplaySubject con 3, por lo que debería guardar en su buffer los últimos 3 valores emitidos.

// Primera Suscripción
this.replaySubject.subscribe(data => {
	console.log('Primera Suscripción:', data);
});

this.replaySubject.next(1);
this.replaySubject.next(2);
this.replaySubject.next(3);
this.replaySubject.next(4);

// Segunda Suscripción
this.replaySubject.subscribe(data => {
	console.log('Segunda Suscripción:', data);
});

this.replaySubject.next(5);
https://s3-us-west-2.amazonaws.com/secure.notion-static.com/70c46a2e-ebc2-4a8a-b058-0ab3f3cbf5e1/Captura_de_pantalla_de_2020-02-17_09-32-38.png

Como podemos ver en la consola, cuando se realiza la segunda suscripción recibe los últimos 3 valores que posee el ReplaySubject (2, 3 y 4). Esta es la gran característica que diferencia al ReplaySubject de los demás.

AsyncSubject:

En este caso, el AsyncSubject no lleva ningún parámetro en su inicialización.

asyncSubject = new AsyncSubject();
// Primera Suscripción
this.asyncSubject.subscribe(data => {
	console.log('Primera Suscripción:', data);
});

this.asyncSubject.next(1);
this.asyncSubject.next(2);

// Segunda Suscripción
this.asyncSubject.subscribe(data => {
	console.log('Segunda Suscripción:', data);
});

this.asyncSubject.complete();
https://s3-us-west-2.amazonaws.com/secure.notion-static.com/733f31b9-93d9-4cc9-8a20-c52a15b14548/Captura_de_pantalla_de_2020-02-17_09-36-38.png

Podemos ver como las dos suscripciones reciben únicamente el valor después de que se realice el complete().


Para concluir, el uso de los subjects puede ser muy beneficioso si lo que queremos es que todos los suscritos reciban los mismos valores. El conocer bien su funcionamiento y las distintas variantes que presenta (en especial el BehaviorSubject y el ReplaySubject) nos dará herramientas para poder afrontar diferentes situaciones.