Software Development

Behind the scenes

The Async Pipe Betrayed Me (But I Thought I Was Careful)

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

Your email address will not be published. Required fields are marked *

About Me

I’m a software developer sharing thoughts, tips, and lessons from everyday coding life — the good, the bad, and the buggy.