Last active
December 18, 2020 15:36
-
-
Save marshalhayes/4990ee31f233ff88b825f689314a1cf6 to your computer and use it in GitHub Desktop.
An extension of the Angular 11 HttpClient that provides a simple http response caching mechanism
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 { HttpClientModule } from '@angular/common/http'; | |
import { NgModule } from '@angular/core'; | |
import { CommonHttpClient } from './http-client.service'; | |
@NgModule({ | |
imports: [ | |
HttpClientModule | |
], | |
providers: [ | |
CommonHttpClient | |
] | |
}) | |
export class CommonHttpClientModule { } |
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 { HttpClient, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http'; | |
import { Injectable } from '@angular/core'; | |
import { EMPTY, Observable } from 'rxjs'; | |
import { switchMap, tap } from 'rxjs/operators'; | |
interface IRequestOptions { | |
headers?: HttpHeaders | { [header: string]: string | string[] }; | |
observe?: 'body' | 'events' | 'response'; | |
params?: HttpParams | { [param: string]: string | string[] }; | |
reportProgress?: boolean; | |
responseType?: 'json' | 'arraybuffer' | 'text' | 'blob'; | |
withCredentials?: boolean; | |
} | |
@Injectable() | |
export class CommonHttpClient extends HttpClient { | |
private readonly cacheStore: Map<string, any> = new Map<string, any>(); | |
/** | |
* Generates a key from the url and query parameters | |
* | |
* @param url | |
* @param options | |
*/ | |
private static generateKeyFromOptions(url: string, options?: IRequestOptions): string { | |
let key = url; | |
if (options?.params) { | |
key += options.params.toString(); | |
} | |
return key; | |
} | |
get<T>(url: string, options?: IRequestOptions, cache?: boolean): Observable<T> { | |
let returnedObservable: Observable<T> = EMPTY; | |
const cacheKey = CommonHttpClient.generateKeyFromOptions(url, options); | |
if (cache) { | |
let data: T; | |
this.tryGetDataFromCache<T>(cacheKey, | |
(cacheData) => { | |
data = cacheData; | |
}, | |
() => { | |
returnedObservable = super.get<T>(url, { | |
responseType: 'json', | |
headers: options?.headers, | |
params: options?.params, | |
observe: 'response', | |
reportProgress: options?.reportProgress, | |
withCredentials: options?.withCredentials | |
}).pipe( | |
switchMap( | |
(value: HttpResponse<T>) => | |
new Observable<T>( | |
subscriber => { | |
subscriber.next(value.body ?? undefined); | |
subscriber.complete(); | |
} | |
).pipe( | |
tap(() => { | |
if (200 <= value.status && value.status <= 299) { | |
this.cacheStore.set(cacheKey, value.body); | |
} | |
}) | |
) | |
) | |
); | |
} | |
); | |
// @ts-ignore: Usage before definition | |
if (data !== undefined) { | |
return new Observable<T>( | |
subscriber => { | |
subscriber.next(data); | |
subscriber.complete(); | |
} | |
); | |
} | |
} else { | |
// cache options was not set, so just pass to the underlying API | |
returnedObservable = super.get<T>(url, { | |
responseType: 'json', | |
headers: options?.headers, | |
params: options?.params, | |
reportProgress: options?.reportProgress, | |
withCredentials: options?.withCredentials | |
}); | |
} | |
return returnedObservable; | |
} | |
/** | |
* Tries to get the requested data | |
* matching key from the cache. When there is a cache hit, | |
* the callback is called with the data from the cache. | |
* | |
* @param key The key to the cache location | |
* @param cb The callback that's called when the key is in the cache | |
* @param otherwise The callback that's called when the key is not in the cache | |
*/ | |
private tryGetDataFromCache<T>(key: string, cb: (cacheData: T) => void, otherwise?: () => void): void { | |
const hasKey = this.cacheStore.has(key); | |
if (hasKey) { | |
cb(this.cacheStore.get(key)); | |
} else if (otherwise) { | |
otherwise(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment