How Angular Handles Subscriptions and Why It Still Caught Me Off Guard
I thought I was careful. I’ve preached it in code reviews, mentioned it in talks, and even put it in documentation: don’t use the | async
pipe multiple times on the same Observable in a template.
And yet, somehow, there I was — debugging ghost API calls and null
references — only to discover I had done exactly that. This is the story of how I got bit by a rule I already understood, how Angular handled it exactly as designed, and why it still surprised me.
The Problem
Some time ago, while working on an Angular component, everything seemed fine—until I noticed multiple HTTP requests firing for a single data source. Checking my template, I found this:
<!-- Separated in the template -->
<h1>{{ (data$ | async)?.name }}</h1>
...
<p>{{ (data$ | async)?.description }}</p>
Because these lines were far apart, I didn’t realize I was calling | async
twice on the same Observable. Each use created a new subscription—and for my cold HTTP Observable, that meant duplicate requests and flaky UI.
The Slip-Up and The Fix
I understood this was bad practice, but in the chaos of development, I duplicated the pipe without noticing. Angular didn’t warn me; it just quietly subscribed twice. The result? Multiple API calls and some confusing UI glitches.
The fix was straightforward — instead of using | async
separately in different parts of the template, I captured the Observable’s emitted value once with *ngIf
and reused it:
<ng-container *ngIf="data$ | async as data">
<h1>{{ data.name }}</h1>
<!-- Later in the template -->
<p>{{ data.description }}</p>
</ng-container>
This way, Angular creates just one subscription, preventing duplicate API calls and stabilizing the UI.
The async pipe subscribes each time it’s used. For cold Observables, that means repeating the data-fetching work. Hot Observables behave differently, but best practice is to avoid multiple async pipes on the same stream anyway.
Takeaways
- Use
| async
once per Observable per template.
- Use
shareReplay
to share cold Observable results.
- Watch for duplicated pipes especially in large or refactored templates.
Conclusion
Sometimes, it’s not what you don’t know, but what you forget, that trips you up. The async pipe didn’t betray me—I betrayed myself with a momentary lapse. Lessons learned, app fixed, and a story to tell.
Leave a Reply