Search code examples
angularcachingangular-http-interceptors

Angular Http Interceptor with caching returns inmutable response


I'm using angular Http Interceptor to catch request and, in case that these request were already made, the interceptor just returns the HttpResponse cached on a service.

The interceptors fires with this piece of code that makes request to the API:

getSeriesWithGenres() {
    const series$ = this.getSeries()
    const genres$ = this.getGenres()

    return forkJoin([series$, genres$]).pipe(
      map(([series, genres]) => {
        return series.map((serie: Serie) => {
          serie.genre_ids = serie.genre_ids.map(genreId => {
            return genres.find((genre: Genre) => genre.id === genreId)?.name
          })

          return serie
        })
      })
    )
}

And the methods getSeries() and getGenres() made the Http Request as follows:

getSeries() {
    return this.http
      .get<ApiResponse>(
        `
        https://api.themoviedb.org/3/tv/popular?api_key=${environment.apiKey}&language=en-US
        `
      )
      .pipe(map((data: ApiResponse) => data.results as Serie[]))
  }

getGenres() {
    return this.http
      .get(
        `
        https://api.themoviedb.org/3/genre/tv/list?api_key=${environment.apiKey}&language=en-US
        `
      )
      .pipe(map((data: any) => data.genres))
}

So, my problem is when I cached the response, because if method called getSeriesWithGenres() fires twice, Iḿ getting the following error:

TypeError: "genre_ids" is read-only

Note: To cached the response I use the following data structure on another service:

private cache: Map<string, [Date | null, HttpResponse<unknown>]> = new Map()

With the corresponding getters and setters methods

And my interceptor, use this service as follows:

const HALF_HOUR = 1800
const TIME_TO_LIVE = HALF_HOUR

@Injectable()
export class CachingInterceptor implements HttpInterceptor {
  constructor(private cacheResolver: CacheResolverService) {}
  intercept(
    request: HttpRequest<unknown>,
    next: HttpHandler
  ): Observable<HttpEvent<unknown>> {
    if (request.method !== 'GET') {
      return next.handle(request)
    }
    console.log('CachingInterceptor: ' + request.url)
    const cachedResponse = this.cacheResolver.get(request.url)
    console.log('CachedResponse:' + JSON.stringify(cachedResponse?.body))
    return cachedResponse ? of(cachedResponse) : this.sendRequest(request, next)
  }

  sendRequest(
    request: HttpRequest<unknown>,
    next: HttpHandler
  ): Observable<HttpEvent<unknown>> {
    return next.handle(request).pipe(
      map((event: HttpEvent<unknown>) => {
        if (event instanceof HttpResponse) {
          this.cacheResolver.set(request.url, event, TIME_TO_LIVE)
        }
        return event
      })
    )
  }
}

Solution

  • The cache sets the content as frozen/read-only, to avoid you tampering with the cache.

    Try updating the stream yourself :

    getSeriesWithGenres() {
        const series$ = this.getSeries()
        const genres$ = this.getGenres()
    
        return forkJoin([series$, genres$]).pipe(
          map(([series, genres]) => series.map(serie => ({
            ...serie,
            genre_ids: serie.genre_ids.map(
              genreId => genres.find((genre: Genre) => genre.id === genreId)?.name
            )
          }))),
        )
    }