Efficient HTTP Caching in Angular with RxJS shareReplay
Avoid duplicate HTTP requests in Angular by using RxJS’s shareReplay to multicast and cache responses. Learn how to set it up, understand its trade‑offs, and implement a refresh strategy.
02 Mar 2026, 23:29 UTC

The Problem of Duplicate HTTP Requests
In a typical Angular application, several components may need the same data – for instance, a user profile that is shown on a dashboard, a sidebar, and a modal. If each component injects a service that calls HttpClient.get() directly, the browser will send a separate GET request for each subscription. This not only wastes bandwidth but can also overwhelm the backend if the endpoint is rate‑limited.
How shareReplay Multicasts and Replays
The shareReplay operator turns a cold observable (one that starts a new HTTP request per subscription) into a hot, shared observable. When the first subscriber arrives, the source is subscribed to and the HTTP call is made. Subsequent subscribers receive the same underlying stream. The operator also keeps a buffer of emitted values and replays the most recent value to new subscribers, so they don’t have to wait for a new HTTP round‑trip.
Typical configuration:
shareReplay({ bufferSize: 1, refCount: true })
- bufferSize: 1 – keeps only the last emitted value.
- refCount: true – automatically unsubscribes from the source when the last subscriber goes away, preventing memory leaks.
Practical Example: A User Service
Below is a minimal Angular service that exposes a shared user observable. The service ensures that only one GET request is sent, even if multiple components subscribe.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
interface User { id: number; name: string; }
@Injectable({ providedIn: 'root' })
export class UserService {
private user$!: Observable;
constructor(private http: HttpClient) {}
getUser(): Observable {
if (!this.user$) {
this.user$ = this.http.get('/api/user').pipe(
shareReplay({ bufferSize: 1, refCount: true })
);
}
return this.user$;
}
}
Components can subscribe without causing a new request:
this.userService.getUser().subscribe(user => {
this.user = user;
});
To verify that only one request is made, open the browser’s Network panel, trigger the two components, and observe a single GET request.
Trade‑offs and Limitations
- Stale Data –
shareReplaykeeps the last value in memory until every subscriber unsubscribes. If the underlying data changes on the server, clients will continue to receive the cached value until the observable is refreshed. - High‑Frequency Streams – For data that changes every few seconds (e.g., WebSocket feeds), a buffer of 1 can quickly become outdated. In such cases, consider setting
bufferSize: 0or using a different strategy (e.g.,switchMapwith a refresh trigger). - Memory Leaks Without refCount – Omitting
refCount: truekeeps the shared observable alive indefinitely, meaning future subscribers will always receive the cached value and the underlying HTTP subscription will never complete. This can lead to memory leaks if the source never completes.
To mitigate stale data, expose a manual refresh method:
private refresh$ = new Subject();
getUser(): Observable {
this.user$ = this.refresh$.pipe(
startWith(void 0),
switchMap(() => this.http.get('/api/user')),
shareReplay({ bufferSize: 1, refCount: true })
);
return this.user$;
}
refreshUser() {
this.refresh$.next();
}
Now a component can call userService.refreshUser() to trigger a fresh HTTP request, ensuring subscribers receive updated data.
Actionable Checklist for Production Use
- Wrap HTTP calls that are shared across components with
shareReplay({ bufferSize: 1, refCount: true }). - Verify that the network panel shows only one request when multiple components subscribe.
- Implement a manual refresh trigger if data can change and you need to avoid stale caching.
- Monitor memory usage in devtools; ensure the observable is garbage‑collected when all subscribers complete.
- For high‑frequency data streams, consider alternative patterns (e.g.,
switchMaporBehaviorSubjectwith explicit polling).
By following this pattern, you reduce network traffic, avoid duplicate calls, and keep the application’s memory footprint in check.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.