Skip to content

Allow customization of HTTP Cache Transfer  #50117

@yjaaidi

Description

@yjaaidi

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 this

Alternatives 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:

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 😉😉 👈

Metadata

Metadata

Assignees

Labels

area: common/httpIssues related to HTTP and HTTP Clientarea: serverIssues related to server-side renderingfeatureLabel used to distinguish feature request from other issuesfeature: under considerationFeature request for which voting has completed and the request is now under considerationserver: http cache

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions