Prevent duplicate HTTP calls in Angular with RxJS shareReplay
Learn how RxJS shareReplay prevents duplicate HTTP requests in Angular by multicasting and caching the latest emission, with a concrete service example and verification steps.
14 Jun 2026, 12:47 UTC

The problem: duplicate requests from multiple subscribers
In an Angular app, several components or services often call the same method on a data service, for example UserService.getProfile(). If that method returns a raw HttpClient.get observable, each subscription triggers a separate HTTP request. The network tab shows multiple identical calls, wasting bandwidth and increasing latency.
Why shareReplay helps
shareReplay is an RxJS operator that multicasts a source observable and replays the most recent emission to new subscribers. Internally it uses a Subject to share the execution and a ReplaySubject buffer to store the last value(s). When refCount is true (the default), the underlying subscription is torn down automatically when the last subscriber unsubscribes, allowing the source to be re‑subscribed on demand.
Worked example: caching a user profile
First, adjust the service method:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { shareReplay } from 'rxjs/operators';
interface UserProfile {
id: number;
name: string;
email: string;
}
@Injectable({ providedIn: 'root' })
export class UserService {
private readonly apiUrl = '/api/profile';
constructor(private http: HttpClient) {}
getProfile() {
return this.http.get(this.apiUrl).pipe(
shareReplay({ bufferSize: 1, refCount: true })
);
}
}
Two components can now subscribe:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { UserService } from '../user.service';
import { Subscription } from 'rxjs';
@Component({ selector: 'app-profile-header', template: '{{ profile?.name }}' })
export class ProfileHeaderComponent implements OnInit, OnDestroy {
profile: UserProfile | null = null;
private sub!: Subscription;
constructor(private userSvc: UserService) {}
ngOnInit() {
this.sub = this.userSvc.getProfile().subscribe(p => (this.profile = p));
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}
When the app runs, open Chrome DevTools → Network tab and reload the page. You should see **only one** request to /api/profile despite two components subscribing.
Trade‑offs and limitations
- Stale data: The cached value lives as long as at least one subscriber exists. If the backend data can change, you may serve outdated information unless you manually reset the cache (e.g., by calling a method that returns a new observable) or use a shorter
bufferTime. - Memory usage: The replay buffer holds the emitted value in memory. For large payloads, consider whether caching is appropriate or combine with
take(1)if you only need the first emission. - Subscription leaks: Forgetting to unsubscribe keeps the internal subscription alive, preventing the source from being re‑subscribed when you actually want a fresh request. Always pair with
takeUntil(this.destroy$)or unsubscribe inngOnDestroy.
Practical verification steps
- Run the Angular development server:
ng serve(executed in the project root; no special permissions needed). - Open the app in a browser, open DevTools → Network, and filter by
XHRorfetch. - Trigger the components that call
getProfile()(e.g., navigate to a route that loads them). - Confirm that only a single request to
/api/profileappears. - Optionally, write a marble test with RxJS TestScheduler to assert that two subscribers receive the same emission while the source subscribes once.
By applying shareReplay to expensive observables, you eliminate duplicate network traffic while keeping the subscription lifecycle simple. Monitor the network tab to verify the behavior and reset the cache when data freshness is required.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.