Angular Caching
Angular
Why Cache
- Improves responsiveness
- Reduces bandwidth and network consumption
- Reduces backend server load
- Reduces redundant computations
Classic Caching Pattern
// product.service.ts:
private products: Product[] = [];
getProducts = () => {
if (this.products) {
return of(this.products);
}
return this.http.get<Product[]>(this.url).pipe(tap((data) => (this.products = data)));
};
Declarative Caching Pattern
private url = 'api/products';
products$ = this.http.get<Product[]>(this.url).pipe(shareReplay(1));
shareReplay
- Replays the defined number of emissions on subscription:
shareReplay(1)
- shareReplay is a multicast operator
- Returns a Subject that shares a single subscription to the underlying source
- Takes in an optional buffer size, which is the number of items cached and replayed
- The items stays cached forever, even after there are no more subscribers
Used for
- Sharing Observables
- Caching data in the application
- Replaying emissions to late subscribers
Add shareReplay to any Observable pipeline you wish to share and replay to all new Subscribers.
Retain retrieved data in a service
- No need for another HTTP request
- Reduces redundant computations
- Improves performance and responsiveness
productCategories$ = this.http.get<ProductCategory[]>(this.url).pipe(shareReplay(1));
share
share({
connector: () => new ReplaySubject(1),
resetOnComplete: false,
resetOnError: false,
resetOnRefCountZero: false,
});
- Similar to shareReplay
- By default, doesn't have a buffer
- Doesn't replay that buffer. This ability to replay values on subscription is what differentiates share and shareReplay.
Cache Invalidation
-
Invalidating the cache on a time interval
-
Always getting fresh data on update operations (same as 3, call
refreshSub.next()
when updating operations) -
Allowing the user to control when data is refreshed
Loading Code Editor
import {HttpClient} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {BehaviorSubject, map, tap, shareReplay, switchMap} from 'rxjs';
@Injectable({providedIn: 'root'})
export class NbaService {
private refreshSub$ = new BehaviorSubject<void>(undefined);
private api = 'https://www.balldontlie.io/api/v1/';
private teamUrl = this.api + 'players';
apiRequest$ = 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(),
}));
}),
);
players$ = this.refreshSub$.pipe(
switchMap(() => this.apiRequest$), // will return a new api instance
shareReplay(1),
);
constructor(private http: HttpClient) {}
refreshPlayers() {
this.refreshSub$.next();
}
}