Cuando hacemos la aplicación, algunos datos como el menú, las opciones no cambian con la frecuencia. El mejor enfoque es almacenarlo en caché, porque cuando el usuario se mueve por la aplicación, buscar los datos en el servidor nuevamente, es algo innecesario e impactan en la velocidad y la experiencia del usuario.
Rxjs nos brinda una manera fácil de construir un caché y almacenarlo, solo usando dos operadores hacen que la magia suceda, share y sharReplay estos permiten hacer llamadas innecesarias o recalcular datos que previamente fueron calculados.
Ejemplo
Tengo una aplicación simple con dos rutas a home y about, home muestra una lista de jugadores de la NBA, procesamos los datos para construir su nombre completo utilizando su primer y segundo nombre.
Cada vez que el usuario mueve entre la página de home y about, nuestro componente necesita obtener los datos y también realizar el proceso.
En otros escenarios esto puede ser un progreso grande y costoso.
¿Por qué estoy obteniendo los datos nuevamente, si no cambian con la frecuencia? Parece que es hora de almacenar en caché.
Usando ShareReplay
Mejoraremos el rendimiento y la respuesta de nuestra aplicación, evitaremos repetir el proceso de compilación fullName para cada jugador, para nuestro ejemplo también añadiremos la fecha del procesamiento, así de una forma visual sabemos cuando fueron procesados.
shareReplay nos ayuda a almacenar datos en caché en nuestras aplicaciones fácilmente y también emitir los datos para nuevos suscriptores.
Puedes leer mas sobre shareReplay
En mi ejemplo emplearemos un servicio, el cual hace una petición a una API para traer los jugadores.
Agregaremos el operador shareReplay en el flujo de datos, y tomaremos la respuesta del http y asignando el shareReplay al final con el número 1 como parámetro emitiremos la última emisión de mi solicitud HTTP.
He realizado un map de los datos agregando dos propiedades fullName que es la concatenación de firstName y lastName, además de crear una nueva propiedad proccesed con la fecha.
@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, para ver los datos en la página, usamos el operador Date pipe para tener un mejor formato de la fecha procesada.
<ul *ngIf="players$ | async as players">
<li *ngFor="let player of players">
{{ player.fullName }} {{ player.processed | date: 'medium' }}
</li>
</ul>
Perfecto, si ahora navegamos en la aplicación de una página a otra y regresamos a la página de inicio, obtendrá los datos del caché, esto puedes verlo en el devtools en el tab de network.
¿De momento todo muy bien, pero como forzamos a actualizar los datos?
Actualizando el caché
Nuestro caché funciona a las mil maravillas, pero a veces los usuarios quieren forzar la actualización, ¿cómo podemos hacerlo? Rxjs siempre busca hacer nuestra vida fácil!
Utilizamos un BehaviorSubject, reaccionar a la acción cuando el usuario desea actualizar los datos.
Primero creamos el behaviorSubject de tipo void y un nuevo método updateData() para emitir la acción, creamos una nueva variable apiRequest$ para almacenar el observable de http.
Nuestro observable player$ obtendrá el valor behaviorSubject y canalizará los datos usando el operador mergeMap para combinar la respuesta http y devolver el observable, al final del proceso agregaremos nuestro shareReplay.
Leer mas sobre mergeMap
El código final será algo 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, agregamos un nuevo botón para llamar al método de servicio y forzar la actualización de los datos que se lanza con el behaviorSubject, puede jugar con la versión final en el ejemplo de stackbliz.
https://stackblitz.com/edit/angular-ivy-hbf6dc
Resumen
En resumen, hemos visto como podemos crear un caché y forzar la actualización tan fácilmente usando Rxjs, ¡así que la próxima vez que quieras mejorar la velocidad y la respuesta es superfácil!
Recomiendo que saques unos minutos ver algunos videos de @deborahk ella explica muy bien todo sobre rxjs y cómo trabajar con datos (en inglés).
- Data Composition with RxJS | Deborah Kurata
- Collect, Combine and Cache RxJS Streams for User-Friendly Results by Deborah Kurata
Photo by Lama Roscu on Unsplash
Top comments (2)
Hola Dani, buenísimo artículo. Me surge una dudilla. La opción de actualizar caché es para si en un casual ese listado sufriera actualizaciones cada X tiempo supongo, ¿no?
Si es una petición que devuelve un listado que no cambia en el tiempo, con la opción del shareReplay solamente sería suficiente para obtenerlo desde cualquier parte sin repetir la llamada al servidor, ¿no?
Exacto, la opción de actualizar la caché es más para por ejemplo si tienes un dashboard que sabes que el backend se actualiza al medio día, pues puedes poner una task que cada 3 horas pues llame el metodo de actualizar cache :)
Pero si tienes un servicio que retorna el menu de tu aplicacion y eso no cambia , pues con el shareReplay pues seria suficiente.