Skip to content

Instantly share code, notes, and snippets.

@marshalhayes
Last active December 18, 2020 15:36
Show Gist options
  • Save marshalhayes/4990ee31f233ff88b825f689314a1cf6 to your computer and use it in GitHub Desktop.
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
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 { }
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