Created
January 25, 2019 13:43
-
-
Save ngnam/3596467a8fc041901a421fdf313c436a to your computer and use it in GitHub Desktop.
Cache interceptor Angular
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
); | |
} | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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