Back to overview

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

  1. Invalidating the cache on a time interval

  2. Always getting fresh data on update operations (same as 3, call refreshSub.next() when updating operations)

  3. 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();
  }
}

Reference