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