|
| 1 | +import { inject, Injectable, REQUEST_CONTEXT } from '@angular/core'; |
1 | 2 | import { Observable, shareReplay } from 'rxjs'; |
2 | 3 |
|
3 | 4 | import { ConfigFileDefinition } from './definition'; |
4 | 5 | import { ConfigFileLoader } from './loader'; |
5 | 6 |
|
| 7 | +@Injectable({ |
| 8 | + providedIn: 'root', |
| 9 | + useFactory: () => |
| 10 | + ConfigFilesPersistentCache.useFromRequestContext() ?? |
| 11 | + new ConfigFilesPersistentCache(), |
| 12 | +}) |
| 13 | +export class ConfigFilesPersistentCache extends Map< |
| 14 | + string, |
| 15 | + Observable<unknown> |
| 16 | +> { |
| 17 | + static readonly key = Symbol('ConfigFilesPersistentCache'); |
| 18 | + |
| 19 | + static useFromRequestContext(): ConfigFilesPersistentCache | null { |
| 20 | + const context = inject(REQUEST_CONTEXT); |
| 21 | + if (!context) return null; |
| 22 | + if (typeof context !== 'object') return null; |
| 23 | + const cache = Reflect.get(context, this.key); |
| 24 | + if (!cache) return null; |
| 25 | + return cache; |
| 26 | + } |
| 27 | + |
| 28 | + constructor() { |
| 29 | + super(); |
| 30 | + } |
| 31 | +} |
| 32 | + |
| 33 | +@Injectable({ providedIn: 'root' }) |
| 34 | +export class ConfigFilesSessionCache extends Map< |
| 35 | + ConfigFileDefinition<unknown, unknown>, |
| 36 | + Observable<unknown> |
| 37 | +> { |
| 38 | + constructor() { |
| 39 | + super(); |
| 40 | + } |
| 41 | +} |
| 42 | + |
6 | 43 | /** |
7 | 44 | * Decorator for {@link ConfigFileLoader} |
8 | 45 | * that caches the loaded configuration objects of each definition. |
| 46 | + * |
| 47 | + * In SSR applications, a persistent cache can be provided from the server |
| 48 | + * so that config files are only loaded once throughout the application |
| 49 | + * lifecycle: |
| 50 | + * ```ts |
| 51 | + * const app = express(); |
| 52 | + * const angularApp = new AngularNodeAppEngine(); |
| 53 | + * const configFilesCache = new ConfigFilesPersistentCache(); |
| 54 | + * ``` |
| 55 | + * ```ts |
| 56 | + * angularApp |
| 57 | + * .handle(req, { [ConfigFilesPersistentCache.key]: configFilesCache }) |
| 58 | + * .then((response) => |
| 59 | + * response ? writeResponseToNodeResponse(response, res) : next(), |
| 60 | + * ) |
| 61 | + * .catch(next); |
| 62 | + * ``` |
9 | 63 | */ |
10 | 64 | export class CacheConfigFiles implements ConfigFileLoader { |
11 | | - #cache = new Map< |
12 | | - ConfigFileDefinition<unknown, unknown>, |
13 | | - Observable<unknown> |
14 | | - >(); |
| 65 | + #sessionCache = inject(ConfigFilesSessionCache); |
| 66 | + #persistentCache = inject(ConfigFilesPersistentCache); |
15 | 67 |
|
16 | 68 | constructor(private kernel: ConfigFileLoader) {} |
17 | 69 |
|
18 | 70 | load<T, Schema>(def: ConfigFileDefinition<T, Schema>): Observable<T> { |
19 | | - const cached = this.#cache.get(def); |
| 71 | + const cached = |
| 72 | + (def.id && this.#persistentCache.get(def.id)) ?? |
| 73 | + this.#sessionCache.get(def); |
20 | 74 | if (cached) return cached as Observable<T>; |
21 | 75 | const result$ = this.kernel.load(def).pipe(shareReplay(1)); |
22 | | - this.#cache.set(def, result$); |
| 76 | + if (def.id) this.#persistentCache.set(def.id, result$); |
| 77 | + this.#sessionCache.set(def, result$); |
23 | 78 | return result$; |
24 | 79 | } |
25 | 80 | } |
0 commit comments