9 min to read
RxJS
RxJS y Observables
Buenasss como andan? Lo prometido es deuda y volvemos con más magia RxJS y Observables! Como siempre acerco la docu oficial acá
El post de hoy va dedicado a experiencias y algunos escenarios con diferentes combinaciones donde vamos a estar aplicando y profundizando estos conceptos.
¿Qué es RxJS y por qué es tan importante?
RxJS, o Reactive Extensions for JavaScript, es una biblioteca que permite aplicar programación reactiva en JavaScript, basicamente para lidiar con operaciones asíncronas, como la gestión de eventos, las solicitudes HTTP y las actualizaciones en tiempo real. El enfoque que se le da a los Observables ofrece una manera estructurada y potente de manejar flujos de datos que cambian con el tiempo. Si no leiste los posts anteriores te sugiero una pasadita por acá
Experiencias y Escenarios
Ejercicio 1: Búsqueda Instantánea
Imaginemos una app con una barra de búsqueda instantánea (Google?). La idea es que a medida que los usuarios escriben en esta barra, lo que queremos es mostrar sugerencias basadas en lo que va escribiendo. Aquí es donde entra en juego nuestro maravilloso RxJS.
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import { ProductService } from './product.service';
@Component({
selector: 'app-product-search',
templateUrl: './product-search.component.html',
})
export class ProductSearchComponent {
searchControl = new FormControl();
suggestions: string[] = [];
constructor(private productService: ProductService) {
this.searchControl.valueChanges
.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((query: string) => this.productService.getSuggestions(query))
)
.subscribe((suggestions: string[]) => {
this.suggestions = suggestions;
});
}
}`
Explicación:
Cuando los usuarios escriben en la barra de búsqueda, queremos proporcionar sugerencias relevantes en tiempo real. Para esto, primero creamos un control de form llamado searchControl
que representa el campo de búsqueda. Usamos una serie de operadores RxJS, como debounceTime
(aguanta la mecha, nos da un tiempo de retraso entre entradas según el valor que especifiquemos para ello), distinctUntilChanged
(con este operador evitamos duplicados consecutivos, es decir, ejecuciones consecutivas incluso cuando el valor no cambia) y switchMap
(manejo de solicitudes asincrónicas, te lo explico en detalles en este post) en la secuencia de eventos del valueChanges
del control.
Con todo esto estamos en condiciones de emitir solicitudes al servidor para obtener sugerencias basadas en la entrada actual del usuario.
Ejercicio 2: Mejorando la Experiencia de Usuario
Podemos referirnos a ciertas interacciones algo complejas, como por ejemplo, cargar contenido según la posición de desplazamiento, lo cual puede mejorar significativamente la experiencia del usuario. Nuevamente, podemos aprovechar la magia de los Observables que resultan ideales para detectar estos eventos de desplazamiento y cargar datos adicionales de manera eficiente.
import { Component, HostListener } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-infinite-scroll',
templateUrl: './infinite-scroll.component.html',
})
export class InfiniteScrollComponent {
items: any[] = [];
constructor(private dataService: DataService) {}
@HostListener('window:scroll', ['$event'])
onScroll() {
const scrolledToBottom = window.innerHeight + window.scrollY >= document.body.offsetHeight;
if (scrolledToBottom) {
this.dataService.loadMoreData().subscribe((newItems: any[]) => {
this.items.push(...newItems);
});
}
}
}`
Explicación:
En este ejercicio, cuando el usuario se desplaza al final de la página, queremos cargar más elementos automáticamente. Utilizamos un Observador en el evento de desplazamiento (@HostListener
) para detectar cuándo el usuario ha llegado al final. Si el usuario ha llegado al final de la página, llamamos a un servicio DataService
para cargar más datos. Los nuevos elementos se agregan a los existentes, lo que permite una experiencia de desplazamiento continuo y sin interrupciones.
Ejercicio 3: Actualizaciones en Tiempo Real con WebSockets
Imaginemos una app de chat en tiempo real, en donde es crucial que las actualizaciones se reflejen instantáneamente. Los Observables se convierten en un recurso fundamental para manejar mensajes entrantes y mantener actualizada la interfaz de usuario.
import { Component } from '@angular/core';
import { WebSocketService } from './websocket.service';
@Component({
selector: 'app-chat',
templateUrl: './chat.component.html',
})
export class ChatComponent {
messages: string[] = [];
constructor(private wsService: WebSocketService) {
this.wsService.connect();
this.wsService.messages.subscribe((message: string) => {
this.messages.push(message);
});
}
sendMessage(message: string) {
this.wsService.sendMessage(message);
}
}`
Explicación:
En este escenario, creamos un servicio WebSocketService
para manejar la conexión con el servidor de WebSocket. La suscripción al Observador messages
nos permite estar al tanto de los mensajes entrantes. Cuando un nuevo mensaje llega, se agrega a messages
, lo que actualiza automáticamente la vista de chat.
Btw, WebSocket amerita su propio post.
Ejercicio 4: Seguimiento y gestión de tareas
Supongamos un caso donde los usuarios pueden agregar y completar tareas. Además, queremos llevar un registro de las mismas. Veamos el ejemplo
import { from, Subject } from 'rxjs';
import { tap, scan, filter } from 'rxjs/operators';
// Flujo de tareas agregadas
const addTaskSubject = new Subject();
// Stream de tareas con estadísticas
const task$ = addTaskSubject.pipe(
scan((tasks, task) => [...tasks, { name: task, completed: false }], []),
tap(tasks => console.log('Tareas:', tasks)),
scan((stats, tasks) => {
const completedTasks = tasks.filter(task => task.completed);
return { total: tasks.length, completed: completedTasks.length };
}, { total: 0, completed: 0 })
);
// Simulamos el agregado de "tareas"
from(['Tomar birra', 'Tomar gin']).subscribe(task => {
addTaskSubject.next(task);
});
// Simulación de completar una tarea
setTimeout(() => {
addTaskSubject.next('Tomar birra');
}, 2000);
// Suscripción para recibir actualizaciones de tareas y estadísticas
task$.subscribe(stats => {
console.log('Estadísticas:', stats);
});
Explicación:
En este caso, creamos un Subject
llamado addTaskSubject
para representar el flujo de tareas agregadas.
Tambien vamos a usar un operador llamado scan
para acumular las tareas en un arreglo y luego un tap
para mostrar las tareas en la consola cada vez que se agrega una tarea.
Volvemos a usar scan
pero esta vez junto con filter
para calcular las estadísticas de tareas completadas en función del arreglo de tareas. Las estadísticas se acumulan en un objeto que contiene el número total de tareas y el número de tareas completadas.
Simulamos el agregado de tareas usando from(['Tomar birra', 'Tomar gin']).subscribe(task => { ... })
. Después de 2 segundos, simulamos la finalización de una tarea llamando a addTaskSubject.next('Tomar birra')
.
Finalmente, nos suscribimos a la secuencia task$
para recibir actualizaciones sobre las tareas y las estadísticas.
Conclusión
RxJS y los Observables pueden aplicarse en situaciones para crear interacciones dinámicas, manejar flujos de datos en tiempo real y mejorar la experiencia del usuario en aplicaciones. Cada caso muestra cómo RxJS permite abordar problemas complejos de manera estructurada y efectiva, lo que contribuye a crear apps más receptivas y atractivas.
Happy coding!