Mejorar el rendimiento con Angular y RxJS: Guía para almacenar datos en caché

Cuando creamos una aplicación, ciertos datos como el menú o las opciones de configuración no cambian con frecuencia. La mejor opción es guardarlos en caché, porque cuando el usuario navega por la app, pedir los mismos datos al servidor una y otra vez afecta la velocidad y la experiencia de usuario.
RxJS nos ofrece una forma muy sencilla de crear una caché y almacenarla. Hay dos operadores clave para esto: share y shareReplay. Con ellos evitamos pedir los datos en cada ocasión y nos ahorramos cálculos innecesarios.
Ejemplo
Tengo una aplicación sencilla con dos rutas: home y about. En home muestro una lista de jugadores de la NBA y proceso los datos generando la propiedad fullName a partir del nombre y apellido de cada jugador.
Cada vez que el usuario sale de home y regresa, la aplicación vuelve a pedir los datos y a ejecutar este procesamiento. En aplicaciones reales, este proceso podría ser mucho más pesado, como cálculos complejos o transformaciones grandes.
¿Por qué volver a pedir los datos si no cambian con frecuencia? Es el momento ideal para aplicar una caché.
Uso de shareReplay
Vamos a mejorar el rendimiento y el tiempo de respuesta de nuestra aplicación. Evitaremos repetir el proceso de armar fullName para cada jugador y guardaremos la fecha del procesamiento para ver exactamente cuándo se ejecutó.
El operador shareReplay nos ayuda a guardar datos en caché de forma rápida y a emitir esa misma respuesta a nuevos suscriptores.
Lee más sobre shareReplay
Al agregar el operador shareReplay en el flujo de datos, tomamos la respuesta de la petición HTTP y la guardamos en un búfer. De esta forma, reemite la última emisión cuando un nuevo componente se suscribe.
@Injectable()
export class NbaService {
api = 'https://www.balldontlie.io/api/v1/';
private teamUrl = this.api + 'players';
public players$ = this.http.get<any[]>(this.teamUrl).pipe(
map((value: any) => {
return value?.data.map((player) => ({
...player,
fullName: `${player.first_name} ${player.last_name}`,
processed: new Date().toISOString(),
}));
}),
shareReplay(1),
);
constructor(private http: HttpClient) {}
}Perfecto, ahora podemos mostrar los datos en la página. Usamos el pipe Date para dar un mejor formato a la fecha de procesamiento.
<ul *ngIf="players$ | async as players">
<li *ngFor="let player of players">
{{ player.fullName }} {{ player.processed | date: 'medium' }}
</li>
</ul>Si navegamos por la aplicación de una página a otra y regresamos a la página de inicio, los datos se obtendrán directamente de la caché.
Puedes ver los detalles en la pestaña Network de Chrome.
https://stackblitz.com/edit/angular-ivy-3vql5l?file=src/app/nba.service.ts
Tener una caché en nuestra aplicación es así de fácil. Pero, ¿cómo podemos forzar una actualización?
Actualizar la caché y refrescar los datos
Nuestra caché funciona muy bien, pero en ocasiones el usuario necesita forzar la actualización. ¿Cómo podemos resolver esto? RxJS nos facilita la vida.
Usamos un BehaviorSubject para reaccionar a la acción del usuario cuando quiera refrescar los datos.
Primero, creamos un BehaviorSubject de tipo void y un método updateData() para emitir la acción. También creamos una variable apiRequest$ para almacenar la petición HTTP.
Nuestro observable players$ escuchará las emisiones del BehaviorSubject y usará el operador mergeMap para conectarse con la respuesta HTTP, retornar el observable y aplicar shareReplay.
Lee más sobre merge.
El código final se ve así:
@Injectable()
export class NbaService {
private _playersData$ = new BehaviorSubject<void>(undefined);
api = 'https://www.balldontlie.io/api/v1/';
private teamUrl = this.api + 'players';
apiRequest$ = this.http.get<any[]>(this.teamUrl).pipe(
map((value: any) => {
console.log('getting data from server');
return value?.data.map((player) => ({
...player,
fullName: `${player.first_name} ${
player.last_name
} ${Date.now().toFixed()}`,
}));
})
);
public players$ = this._playersData$.pipe(
mergeMap(() => this.apiRequest$),
shareReplay(1)
);
constructor(private http: HttpClient) {}
updateData() {
this._playersData$.next();
}
}En la página creamos un botón para llamar al método del servicio y forzar la actualización de los datos disparando el BehaviorSubject. Puedes probar la versión interactiva en el ejemplo de StackBlitz.
¡Listo! Puedes ver el código final.
Resumen
Podemos crear una caché y forzar su actualización de forma muy sencilla con RxJS. Es una excelente alternativa para la próxima vez que quieras optimizar la velocidad y el tiempo de respuesta.
Te recomiendo ver estos videos de @deborahk, donde explica muy bien cómo trabajar con flujos de datos en RxJS:
- Data Composition with RxJS | Deborah Kurata
- Collect, Combine, and Cache RxJS Streams for User-Friendly Results by Deborah Kurata
Photo by Julia Zolotova on Unsplash
Modern frontend development — Angular, React, TypeScript, accessibility, and performance.
Artículos Relacionados
Formularios en Angular: Cómo elegir entre Template-Driven, Reactive y Signal-Driven Forms
Una comparación práctica entre Template-Driven, Reactive y Signal-Driven Forms en Angular. Aprende a crear formularios con validación fuertemente tipada, control flow moderno y estado computado con Signals.
Mis mejores artículos sobre Angular y Kendo de 2023
Al terminar 2023, reflexiono sobre las valiosas lecciones y conocimientos que he adquirido escribiendo. Además de este blog, creo contenido para Telerik Kendo. Hoy me alegra compartir mis mejores artículos sobre Kendo UI de este año.
Compartir este artículo
Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.
Software real. Lecciones reales.
Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.
Únete a 13,800+ desarrolladores y lectores.
Cero spam. Cancela tu suscripción en cualquier momento.