# BrowserCacheService **File**: `src/app/domain/services/browser-cache.service.ts` A generic, injectable Angular service that provides a typed read/write/invalidate API over the browser's [Cache Storage API](https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage). Intended as a shared foundation for any feature that wants to cache HTTP responses across navigation events without a Service Worker. --- ## Why Cache Storage? | Mechanism | Survives navigation | Survives page reload | Configurable TTL | Storage limit | |---|---|---|---|---| | Component state | ✗ | ✗ | — | Memory | | NgRx store | ✓ (same tab) | ✗ | — | Memory | | `sessionStorage` | ✓ | ✗ | Manual | ~5 MB | | `localStorage` | ✓ | ✓ | Manual | ~5 MB | | **Cache Storage** | ✓ | ✓ | ✓ (per entry) | Quota-managed | Cache Storage was chosen because it: - Is available in all modern browsers (Chrome 40+, Firefox 44+, Safari 11.1+) - Stores structured data alongside an expiry timestamp without size pressure - Is already used by Service Workers and the browser's native HTTP cache, so quota management is handled by the browser - Falls back gracefully (service becomes a no-op) when unavailable --- ## API ```typescript @Injectable({ providedIn: 'root' }) class BrowserCacheService { get(cacheName: string, key: string, maxAgeMs?: number): Observable put(cacheName: string, key: string, data: T): void invalidate(cacheName: string): void } ``` ### `get(cacheName, key, maxAgeMs?)` Returns an `Observable` that emits the cached value (`T`) or `null` when: - The Cache Storage API is unavailable (e.g. older browser, unit test environment) - No entry exists for the given `cacheName` + `key` combination - The entry is older than `maxAgeMs` (default: `60 000` ms / 1 minute) Errors from the Cache API are caught and converted to `null` — they never propagate to the caller. ### `put(cacheName, key, data)` Stores `data` in the named cache bucket under `key`. A `cachedAt` timestamp is embedded alongside the data so staleness can be checked on the next `get`. Fire-and-forget: errors are silently swallowed. ### `invalidate(cacheName)` Deletes the **entire** Cache Storage bucket for `cacheName`. This removes all entries for that feature in one call. Fire-and-forget: errors are silently swallowed. --- ## Cache key format Internally, entries are stored under a pseudo-URL: ``` /browser-cache/? ``` This keeps entries within a single Cache Storage bucket readable via browser DevTools (Application → Cache Storage). --- ## Adding a new feature cache Create a typed facade service that delegates to `BrowserCacheService`. This keeps the cache name and TTL in one place and gives callers a clean domain API. ```typescript // src/app/domain/services/customer-cache.service.ts import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { BrowserCacheService } from './browser-cache.service'; import { ICustomer } from '../../customers/models/customer.model'; const CACHE_NAME = 'agm-customer-list-v1'; const MAX_AGE_MS = 60_000; // 1 minute @Injectable({ providedIn: 'root' }) export class CustomerCacheService { constructor(private readonly browserCache: BrowserCacheService) {} get(queryParams: string): Observable { return this.browserCache.get(CACHE_NAME, queryParams, MAX_AGE_MS); } put(queryParams: string, data: ICustomer[]): void { this.browserCache.put(CACHE_NAME, queryParams, data); } invalidate(): void { this.browserCache.invalidate(CACHE_NAME); } } ``` Then, in the corresponding service: ```typescript // In CustomerService.loadCustomers(): const cacheKey = params.toString(); return this.customerCache.get(cacheKey).pipe( switchMap(cached => { if (cached !== null) return of(cached); return this.http.get(this.url, { params }).pipe( tap(data => this.customerCache.put(cacheKey, data)) ); }) ); ``` And in the effects, call `this.customerCache.invalidate()` after any create / update / delete action succeeds. --- ## Existing implementations | Feature | Facade | Cache name | TTL | |---|---|---|---| | Job list | `JobCacheService` | `agm-jobs-list-v1` | 60 s | --- ## Versioning the cache name Append a version suffix (e.g. `-v1`, `-v2`) to `cacheName` whenever the shape of the stored data changes. The old bucket will be orphaned in the browser until the browser's quota manager evicts it, or you can explicitly delete the old name during app initialisation. --- ## Browser DevTools Cached entries are visible under: **Chrome DevTools** → Application tab → Cache Storage → `agm-jobs-list-v1`