-
Notifications
You must be signed in to change notification settings - Fork 27.1k
Description
Which @angular/* package(s) are relevant/related to the feature request?
common
Description
One of the most interesting features of Angular's hydration is HTTP Cache Transfer which currently caches responses of all GET/HEAD HTTP requests.
The current limitation is that there doesn't seem any built-in way of overriding the behavior except using the withNoHttpTransferCache() feature when calling provideClientHydration() in order to totally disable the feature.
It would be nice if we could override this behavior for the following use cases:
- A. caching other HTTP methods (e.g. "Safe" use of POST for batching or sending large query strings, maybe even GraphQL which would probably need its own custom transfer) and in that case the key computation will also be different.
- B. disabling the cache transfer for some specific requests or origins:
- B.0. state transfer is handled manually (e.g. in order to reduce the amount of transferred data as the HTTP response can be way larger than the data really needed)
- B.1. private network or internal APIs but publicly hosted SSR
- B.2. device-specific response (e.g. tracking & debugging... get my IP address... (then it shouldn't be a GET request 😉) )
- B.3. emit cached response + new request (e.g. in SSG cases, we might want to fetch fresh data in case SSG-ed is not up-to-date).
Proposed solution
IMO, not all of these use cases should be handled by Angular's hydration's HTTP cache transfer.
Allowing per-request opt-out would be enough to let developers customize the behavior for unconventional requests using TransferState.
This could be done globally using a feature:
provideClientHydration(withHttpTransferCache({filter: req => !req.url.startsWith('https://internal')}));and per request
http.get('/my-ip-address', {cacheTransfer: false}); // we'll have to figure out a better naming for thisAlternatives considered
Disable the feature using withNoHttpTransferCache() and implement our own transfer logic like before using TransferState.
Right now, we can easily mimic the current behavior which is here:
angular/packages/common/http/src/transfer_cache.ts
Lines 35 to 90 in 47c093a
| export function transferCacheInterceptorFn( | |
| req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> { | |
| const {isCacheActive} = inject(CACHE_STATE); | |
| // Stop using the cache if the application has stabilized, indicating initial rendering | |
| // is complete. | |
| if (!isCacheActive || !ALLOWED_METHODS.includes(req.method)) { | |
| // Cache is no longer active or method is not HEAD or GET. | |
| // Pass the request through. | |
| return next(req); | |
| } | |
| const transferState = inject(TransferState); | |
| const storeKey = makeCacheKey(req); | |
| const response = transferState.get(storeKey, null); | |
| if (response) { | |
| // Request found in cache. Respond using it. | |
| let body: ArrayBuffer|Blob|string|undefined = response.body; | |
| switch (response.responseType) { | |
| case 'arraybuffer': | |
| body = new TextEncoder().encode(response.body).buffer; | |
| break; | |
| case 'blob': | |
| body = new Blob([response.body]); | |
| break; | |
| } | |
| return of( | |
| new HttpResponse({ | |
| body, | |
| headers: new HttpHeaders(response.headers), | |
| status: response.status, | |
| statusText: response.statusText, | |
| url: response.url, | |
| }), | |
| ); | |
| } | |
| // Request not found in cache. Make the request and cache it. | |
| return next(req).pipe( | |
| tap((event: HttpEvent<unknown>) => { | |
| if (event instanceof HttpResponse) { | |
| transferState.set<TransferHttpResponse>(storeKey, { | |
| body: event.body, | |
| headers: getHeadersMap(event.headers), | |
| status: event.status, | |
| statusText: event.statusText, | |
| url: event.url || '', | |
| responseType: req.responseType, | |
| }); | |
| } | |
| }), | |
| ); | |
| } |
The modularity of the current design (thanks to the TransferState primitive) and the workaround described here is sufficient enough to reduce the priority of this issue drastically...
👉 ... making it an interesting opportunity for a community initiative 😉😉 👈