Skip to content

Instantly share code, notes, and snippets.

@ngnam
Created January 25, 2019 13:43
Show Gist options
  • Save ngnam/3596467a8fc041901a421fdf313c436a to your computer and use it in GitHub Desktop.
Save ngnam/3596467a8fc041901a421fdf313c436a to your computer and use it in GitHub Desktop.
Cache interceptor Angular
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpInterceptor,
HttpHandler,
HttpRequest,
HttpResponse
} from '@angular/common/http';
import { Observable, Subscriber } from 'rxjs';
import { HttpCacheService } from './http-cache.service';
/**
* Caches HTTP requests.
* Use ExtendedHttpClient fluent API to configure caching for each request.
*/
@Injectable()
export class CacheInterceptor implements HttpInterceptor {
private forceUpdate = false;
constructor(private httpCacheService: HttpCacheService) {}
/**
* Configures interceptor options
* @param options If update option is enabled, forces request to be made and updates cache entry.
* @return The configured instance.
*/
configure(options?: { update?: boolean } | null): CacheInterceptor {
const instance = new CacheInterceptor(this.httpCacheService);
if (options && options.update) {
instance.forceUpdate = true;
}
return instance;
}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (request.method !== 'GET') {
return next.handle(request);
}
return new Observable((subscriber: Subscriber<HttpEvent<any>>) => {
const cachedData = this.forceUpdate
? null
: this.httpCacheService.getCacheData(request.urlWithParams);
if (cachedData !== null) {
// Create new response to avoid side-effects
subscriber.next(new HttpResponse(cachedData as Object));
subscriber.complete();
} else {
next.handle(request).subscribe(
event => {
if (event instanceof HttpResponse) {
this.httpCacheService.setCacheData(
request.urlWithParams,
event
);
}
subscriber.next(event);
},
error => subscriber.error(error),
() => subscriber.complete()
);
}
});
}
}
import { Injectable } from '@angular/core';
import { HttpResponse } from '@angular/common/http';
import { each } from 'lodash';
import { Logger } from '../logger.service';
const log = new Logger('HttpCacheService');
const cachePersistenceKey = 'httpCache';
export interface HttpCacheEntry {
lastUpdated: Date;
data: HttpResponse<any>;
}
/**
* Provides a cache facility for HTTP requests with configurable persistence policy.
*/
@Injectable()
export class HttpCacheService {
private cachedData: { [key: string]: HttpCacheEntry } = {};
private storage: Storage | null = null;
constructor() {
this.loadCacheData();
}
/**
* Sets the cache data for the specified request.
* @param url The request URL.
* @param data The received data.
* @param lastUpdated The cache last update, current date is used if not specified.
*/
setCacheData(url: string, data: HttpResponse<any>, lastUpdated?: Date) {
this.cachedData[url] = {
lastUpdated: lastUpdated || new Date(),
data: data
};
log.debug(`Cache set for key: "${url}"`);
this.saveCacheData();
}
/**
* Gets the cached data for the specified request.
* @param url The request URL.
* @return The cached data or null if no cached data exists for this request.
*/
getCacheData(url: string): HttpResponse<any> | null {
const cacheEntry = this.cachedData[url];
if (cacheEntry) {
log.debug(`Cache hit for key: "${url}"`);
return cacheEntry.data;
}
return null;
}
/**
* Gets the cached entry for the specified request.
* @param url The request URL.
* @return The cache entry or null if no cache entry exists for this request.
*/
getHttpCacheEntry(url: string): HttpCacheEntry | null {
return this.cachedData[url] || null;
}
/**
* Clears the cached entry (if exists) for the specified request.
* @param url The request URL.
*/
clearCache(url: string): void {
delete this.cachedData[url];
log.debug(`Cache cleared for key: "${url}"`);
this.saveCacheData();
}
/**
* Cleans cache entries older than the specified date.
* @param expirationDate The cache expiration date. If no date is specified, all cache is cleared.
*/
cleanCache(expirationDate?: Date) {
if (expirationDate) {
each(this.cachedData, (value: HttpCacheEntry, key: string) => {
if (expirationDate >= value.lastUpdated) {
delete this.cachedData[key];
}
});
} else {
this.cachedData = {};
}
this.saveCacheData();
}
/**
* Sets the cache persistence policy.
* Note that changing the cache persistence will also clear the cache from its previous storage.
* @param persistence How the cache should be persisted, it can be either local or session storage, or if no value is
* provided it will be only in-memory (default).
*/
setPersistence(persistence?: 'local' | 'session') {
this.cleanCache();
this.storage =
persistence === 'local' || persistence === 'session'
? window[persistence + 'Storage']
: null;
this.loadCacheData();
}
private saveCacheData() {
if (this.storage) {
this.storage[cachePersistenceKey] = JSON.stringify(this.cachedData);
}
}
private loadCacheData() {
const data = this.storage ? this.storage[cachePersistenceKey] : null;
this.cachedData = data ? JSON.parse(data) : {};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment