· 5 years ago · Feb 24, 2021, 07:54 PM
1import { Injectable } from "@angular/core";
2import { Subject, Observable } from "rxjs";
3import { of } from "rxjs";
4import { tap } from "rxjs/operators";
5
6@Injectable({
7 providedIn: "root"
8})
9export class CacheService<T> {
10 private cache: Map<string, CacheContent<T>> = new Map<string, CacheContent<T>>();
11 private inFlightObservables: Map<string, Subject<T>> = new Map<string, Subject<T>>();
12 readonly DEFAULT_MAX_AGE: number = 300000;
13
14 constructor() { }
15
16 /**
17 * Gets the value from cache if the key is provided.
18 * If no value exists in cache, then check if the same call exists
19 * in flight, if so return the subject. If not create a new
20 * Subject inFlightObservable and return the source observable.
21 */
22 get(key: string, fallback?: Observable<T>, maxAge?: number): Observable<T> | Subject<T> {
23
24 if (this.hasValidCachedValue(key)) {
25 console.log(`%cGetting from cache ${key}`, "color: green");
26 return of(this.cache.get(key).value);
27 }
28
29 if (!maxAge) {
30 maxAge = this.DEFAULT_MAX_AGE;
31 }
32
33 if (this.inFlightObservables.has(key)) {
34 return this.inFlightObservables.get(key);
35 } else if (fallback && fallback instanceof Observable) {
36 this.inFlightObservables.set(key, new Subject());
37 console.log(`%c Calling api for ${key}`, "color: purple");
38 return fallback.pipe(
39 tap((value) => { this.set(key, value, maxAge); })
40 );
41 }
42 }
43
44 set(key: string, value: T, maxAge: number = this.DEFAULT_MAX_AGE): void {
45 this.cache.set(key, { value: value, expiry: Date.now() + maxAge });
46 this.notifyInFlightObservers(key, value);
47 }
48
49 has(key: string): boolean {
50 return this.cache.has(key);
51 }
52
53 /**
54 * Publishes the value to all observers of the given
55 * in progress observables if observers exist.
56 */
57 private notifyInFlightObservers(key: string, value: T): void {
58 if (this.inFlightObservables.has(key)) {
59 const inFlight = this.inFlightObservables.get(key);
60 const observersCount = inFlight.observers.length;
61 if (observersCount) {
62 console.log(`%cNotifying ${inFlight.observers.length} flight subscribers for ${key}`, "color: blue");
63 inFlight.next(value);
64 }
65 inFlight.complete();
66 this.inFlightObservables.delete(key);
67 }
68 }
69
70 private hasValidCachedValue(key: string): boolean {
71 if (this.cache.has(key)) {
72 // if (this.cache.get(key).expiry < Date.now()) {
73 // this.cache.delete(key);
74 // }
75 return true;
76 } else {
77 return false;
78 }
79 }
80}
81
82interface CacheContent<T> {
83 expiry: number;
84 value: T;
85}
86