It was just another regular Tuesday — coffee in hand, confidence high, ready to knock out a few Angular components. Nothing fancy. Just your usual ngOnInit, a couple of subscribe() calls to some observables, and voilà! Everything worked like a charm. Until it didn’t.
The Problem
A few days later, I noticed something odd. My app was slowing down. Dev Tools showed creeping memory usage after every navigation. Something was… off. Then came the horrifying realization: I subscribed in ngOnInit — and my memory usage exploded.
I had been subscribing to observables without properly cleaning them up. Every navigation piled up ghost subscriptions that kept emitting behind the scenes—classic memory leaks in Angular.
The Solution
In hindsight, the solution was clear: use a notifier with takeUntil, or leverage the async pipe or take(1) where appropriate. Here’s how I cleaned up my act:
takeUntil for ongoing streams
private destroy$ = new Subject<void>();
ngOnInit(): void {
this.someObservable$
.pipe(takeUntil(this.destroy$))
.subscribe(data => {
// handle data
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
async pipe to let Angular handle it
<div *ngIf="someObservable$ | async as data">
{{ data }}
</div>
take(1) for one-time data
Sometimes, you only need a single value — like when fetching a user’s info just once on load. In that case, take(1) is your best friend:
ngOnInit(): void {
this.userService.getCurrentUser()
.pipe(take(1))
.subscribe(user => {
this.user = user;
});
}
This makes sure the subscription completes after the first value — no cleanup required.
Deeper Explanation
When you manually subscribe and forget to unsubscribe, those observers outlive the component. They continue holding onto memory, even after the component is gone. That’s how leaks creep in.
Angular’s own documentation confirms this behavior of the async pipe (source):
“When the component gets destroyed, the
asyncpipe unsubscribes automatically to avoid potential memory leaks.”
Best Practices
- Always unsubscribe in
ngOnDestroyif you manually subscribe. - Use
takeUntilfor ongoing or long-lived streams. - Use
take(1)when you only need the first value. - Prefer the
asyncpipe—less boilerplate, no cleanup fuss. - Occasionally profile memory using Chrome DevTools to catch leaks early.
Conclusion
That one forgotten unsubscribe() taught me more than any guide — memory leaks are sneaky, but fixable. If your app starts feeling sluggish and you’ve “just been subscribing” all over, pause and clean up.
Lesson learned: subscribe wisely, unsubscribe religiously.
Got your own memory leak story? I’d love to hear how you solved it.




Leave a Reply