Skip to content

Instantly share code, notes, and snippets.

@jsanta
Created March 21, 2022 14:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jsanta/ffcc2e67f9c8de5c378ed853566494ae to your computer and use it in GitHub Desktop.
Save jsanta/ffcc2e67f9c8de5c378ed853566494ae to your computer and use it in GitHub Desktop.
Angular cache service + TTL & cache interceptor
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
// Ref https://medium.com/@AurelienLeloup/cache-http-requests-with-rxjs-for-angular-eb9bee93824d
type CacheItem<T> = {
date : Date,
value: Observable<T>
};
@Injectable({
providedIn: 'root'
})
export class AbstractCacheService<T> {
readonly CACHE_DURATION_IN_MINUTES = 5;
readonly DEFAULT_KEY = 'DEFAULT';
// Uso de Map preferido por sobre un objeto JSON por un tema de rendimiento
// map.has(key) es más rápido que obj.hasOwnProperty(key) o que obj[key] !== undefined
// Por otro lado Map permite tener llaves de cualquier tipo.
private cache: Map<string | object, CacheItem<T>> = new Map();
getValue(object?: any): Observable<T> | null {
const now = new Date();
const key: string | object = object ? object : this.DEFAULT_KEY;
const item: CacheItem<T> | undefined = (this.cache.has(key)) ? this.cache.get(key) : undefined;
if (!item) {
return null;
}
if (now.getTime() - item.date.getTime() > this.CACHE_DURATION_IN_MINUTES * 60 * 1000) {
return null;
}
return item.value;
}
setValue(value: Observable<T>, object?: any) {
const key = object ? object : this.DEFAULT_KEY;
const date = new Date();
this.cache.set(key, {date, value});
}
clearCache() {
this.cache.clear();
}
}
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
HttpContext,
HttpContextToken,
HttpResponse
} from '@angular/common/http';
import { Observable, of, shareReplay, tap } from 'rxjs';
import { AbstractCacheService } from '../services/abstract-cache.service';
import { LoggerService } from '../services/logger.service';
@Injectable({
providedIn: 'root'
})
export class HttpCacheService extends AbstractCacheService<HttpResponse<unknown>> {
}
const CACHE_IT = new HttpContextToken<boolean>(() => false);
export function cacheIt() {
return new HttpContext().set(CACHE_IT, true);
}
let Log: LoggerService;
// Ref. https://blog.logrocket.com/caching-with-httpinterceptor-in-angular/
// https://netbasal.com/new-in-angular-v12-passing-context-to-http-interceptors-308a1ca2f3dd
@Injectable()
export class CacheHttpInterceptor implements HttpInterceptor {
constructor(
private cache: HttpCacheService,
private log: LoggerService) {
Log = this.log;
}
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
const requestUrl = request.url;
if(request.context.get(CACHE_IT)) {
let cachedData$ = this.cache.getValue(requestUrl);
if(cachedData$) {
Log.log(['Cached response: ', requestUrl]);
return cachedData$;
} else {
cachedData$ = next.handle(request).pipe(
tap((requestResponse: any): void => {
this.cache.setValue(of(requestResponse as HttpResponse<unknown>), requestUrl);
}),
shareReplay(1)
)
}
return cachedData$;
}
return next.handle(request);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment