Using shareReplay to Cache HTTP Requests in Angular Services
Learn how RxJS shareReplay can cache HTTP requests in Angular services, preventing duplicate calls and managing subscriptions safely.
08 Oct 2025, 16:02 UTC

The problem: duplicate HTTP calls
\nWhen several Angular components inject a service that returns an Observable from HttpClient, each subscription triggers a new HTTP request. This can waste bandwidth and cause inconsistent UI states if the data changes between calls.
How shareReplay solves it
\nThe shareReplay operator turns a cold observable into a connectable one that multicasts the source and replays a configurable number of recent emissions to new subscribers. By keeping a single underlying subscription, the HTTP request is executed only once, and all subscribers receive the same cached value.
Configuration options and trade‑offs
\nThe operator accepts an options object with three relevant fields:
\n- \n
bufferSize– number of values to replay (e.g.,1for the latest emission). \nwindowTime– maximum age of replayed values in milliseconds. \nrefCount– whentrue, the underlying subscription is disposed when the last subscriber unsubscribes, preventing leaks. \n
A common pattern for HTTP caching is shareReplay({ bufferSize: 1, refCount: true }). This behaves like a lazy singleton: the request runs on first subscribe, the result is kept, and the source is torn down when no components need it.
Trade‑off: If refCount is omitted or set to false, the underlying subscription lives forever, keeping the HTTP connection open and potentially holding onto memory. Large bufferSize or long windowTime values can cause excessive memory consumption for high‑frequency streams.
Worked example: a user profile service
\nimport { Injectable } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Observable, shareReplay } from 'rxjs';\n\n@Injectable({ providedIn: 'root' })\nexport class UserService {\n private readonly apiUrl = '/api/user';\n\n /** Returns a cached observable of the current user profile. */\n getProfile(): Observable {\n return this.http.get(this.apiUrl).pipe(\n shareReplay({ bufferSize: 1, refCount: true })\n );\n }\n\n constructor(private http: HttpClient) {}\n}\n\nTwo components can now inject UserService and subscribe to getProfile() without causing duplicate network calls:
this.userService.getProfile().subscribe(profile => this.profile = profile);\n\nThe first subscription triggers the HTTP request; the second receives the replayed value instantly.
\nVerification steps
\n- \n
- Open the browser’s DevTools Network tab. \n
- Navigate to a page that creates two components injecting
UserService. \n - Observe that only one request to
/api/userappears, regardless of how many components subscribe. \n - Unsubscribe from both components (e.g., by navigating away) and verify that the underlying socket is closed – this indicates
refCountreleased the source. \n
These steps rely on observable behavior; they do not guarantee correctness in every edge case but provide a practical sanity check.
\nLimitations and when to avoid
\nIf the HTTP endpoint returns data that can change independently of the user’s session (e.g., a shared resource list), caching with shareReplay may serve stale information. In such cases, consider:
- \n
- Using a shorter
windowTimeto expire the cache. \n - Manually refetching via a
refresh()method that creates a new observable. \n - Avoiding
shareReplayaltogether and letting each component request fresh data. \n
Actionable closing
\nFor Angular services that wrap idempotent HTTP calls, shareReplay({ bufferSize: 1, refCount: true }) is a low‑effort way to eliminate duplicate requests while automatically cleaning up resources. Test the network behavior in your specific UI flow, adjust the buffer or time window if freshness matters, and monitor memory usage in profiling tools to ensure the trade‑off aligns with your application’s performance goals.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.