Empower growth and innovation with the latest Front-end Dev insights

In 2026, Do RxJS Subscriptions in Angular Really Need to Be Manually Unsubscribed One by One?

Sep 9, 2026 Read: 27

Not every RxJS subscription in Angular requires manual cancellation. Strictly speaking, only subscriptions that do not complete automatically and whose callbacks still reference the component instance can cause components to be retained and not garbage collected. By 2026, the mainstream approach in project delivery has become: first answer 'when and by whom will this Observable terminate'; delegate most template bindings to the async pipe, ordinary in-component subscriptions to takeUntilDestroyed, and reserve manual unsubscribe only for rare cases that need to stop at a specific business moment. Remembering this conclusion can reduce a lot of boilerplate code and review debate.

First Determine: Which Subscriptions Do Not Automatically Complete

Memory leaks typically require two conditions: first, the Observable remains active for a long time and never completes; second, the subscription callback still holds a reference to the component instance or objects referenced by the component. In practical debugging, it is not enough to only care about whether the Observable completes, because callbacks still execute after the component is destroyed, which leads to two visible issues: one is that properties of a destroyed component may be modified, often producing warnings in the console; another is that methods or injected services within the component may be triggered again, causing extra requests or state pollution.

Observables can therefore be divided into two categories:

  • Finite streams that end naturally, such as responses from HttpClient, or ordinary requests like first(), take(1), of(), etc., generally do not occupy subscriptions for a long time.
  • Infinite streams that do not end automatically, such as interval, fromEvent, Subject, merge, switchMap and other event or push sources; these also do not complete on their own and need to be stopped externally.

But note: even a naturally completing HttpClient request, if the response is slow enough and the user has already left the page, the callback may still execute this.loading = false after component destruction. While this may not cause a memory leak, it can trigger change detection warnings. Therefore what ultimately needs to be handled is not 'all subscriptions', but 'all callbacks that may still run after component destruction'.

A Three-Step Checklist: Whether a Subscription Requires Manual Unsubscription

Rather than memorizing 'every subscribe needs an unsubscribe', it is better to follow the three steps below in order during code review; mixing up the order can lead to misjudgment:

  1. First, check whether the source completes on its own. If yes, it can usually be excluded as a memory leak candidate; if the callback still retains component references, the issue becomes 'async callback timing' rather than 'subscription leak'.
  2. Second, check whether the callback references the component instance or DOM. If the callback only calls standalone services and global state, and does not reference this or template elements, then even if the Observable lives long, the component instance can still be garbage collected, so no specific unsubscription is needed. But if it references this.xxx within the class, proceed to the next step.
  3. Finally, see if there is a cleaner approach within the lifecycle. After Angular 16, there is takeUntilDestroyed, and templates can directly use the async pipe. As long as the subscription can be tied to the component lifecycle, manual unsubscription is not needed. Only when a subscription must be stopped early for business reasons, or cannot be expressed by lifecycle-based approaches, should you place an unsubscribe.

These three steps correspond to 'whether to worry about leaks', 'who the leak affects', and 'whether there is an alternative without writing manual code'. Once you go through all three steps, your subscription decomposition becomes much clearer.

Comparison of Approaches: Manual unsubscribe, async pipe, and takeUntilDestroyed

None of these three methods is inherently superior; they solve different problems. Below is their typical division of labor, which you can use as an acceptance checklist:

  • Manual unsubscribe: Suitable for subscriptions that must be stopped at a specific business moment, such as polling every few seconds after clicking 'Start Polling' and immediately unsubscribing when 'Stop Polling' is clicked. The advantage is precise control over when to stop; the drawback is that unsubscribe calls are scattered throughout business logic and can be easily missed when branches increase.
  • async pipe: Suitable for Observables directly displayed in templates. Used like data$ | async, it automatically unsubscribes when the component is destroyed, making it the simplest logic; but it is not suitable for scenarios where you need to proactively get the current value inside a component method.
  • takeUntilDestroyed: Suitable for most subscriptions in components or services that need to be registered and then destroyed with the host, such as listening to scroll with fromEvent or polling with interval, paired with takeUntilDestroyed. It is provided as a standalone API after Angular 16, so you must confirm the project's Angular version before using it. It reduces scattered unsubscribe calls, but not all versions support it; in older versions you can use takeUntil with a manually injected destroyed$ variable.

Based on the dozen-plus mid-to-back-office projects we have delivered, a typical range is that 60% to 80% of subscriptions can be covered by the async pipe or takeUntilDestroyed, while the remaining 10% to 20% truly need manual unsubscription. If the proportion of manual unsubscriptions noticeably exceeds 30%, it usually means that streams that should have ended automatically within the lifecycle were instead written as business-driven manual stops.

Project Field: A Subscription Cleanup That Cost Half a Day to a Full Day of Rework

At the beginning of 2026, we conducted a performance overhaul of an Angular admin system that had been running for three years. The constraints: no changes to business behavior, a deployment window of only two days, hundreds of direct subscribe calls scattered in the codebase, no uniform disposal strategy, and steadily growing browser memory.

We proceeded in four steps: First, we enumerated all subscriptions and classified them by 'whether they reside in a service, whether they auto-complete, and whether they reference component fields'. Second, we changed all fields in templates that directly consume Observables to use the async pipe. Third, we migrated all infinite streams within components to takeUntilDestroyed. Finally, we kept only three polling subscriptions that needed to be stopped mid-stream for business reasons, and left them on manual unsubscribe.

Memory dropped noticeably, but there was an unexpected issue after the first cutover: three pages relied on subscriptions that remained alive after leaving the page to update todo badges on other pages. Once takeUntilDestroyed ended those subscriptions early, the badges no longer refreshed automatically. We had to move that cross-page data flow to a global singleton Subject and let other pages subscribe on demand. The rework took approximately half a day to a full day. That was the real cost. In our experience, if the refactoring had not included an audit of whether subscriptions were meant to survive across pages, the rework time would add an extra half a day to a full day, exceeding the reserved window.

Common Pitfalls and Acceptance Criteria

There are three common pitfalls. The first is applying a blanket rule: making every subscribe include an unsubscribe, even for HttpClient, which increases code volume and reviewer burden while providing little benefit. The second is only examining memory snapshots while ignoring problems caused by callbacks executing after destruction, such as repeated API calls or the ExpressionChangedAfterItHasBeenCheckedError after setting properties on a destroyed component. The third is using takeUntilDestroyed in dynamically created services while misunderstanding the overall lifecycle, which may prematurely end a shared stream and affect other components.

For acceptance, go through the following four points in order: First, after entering and leaving the page several times, memory snapshots do not continuously grow; second, there are almost no bare subscribe(() => {}) calls visible in component classes, and places that require subscriptions all have a unified disposal mechanism; third, async data bindings in templates mostly come from the async pipe, rather than manual assignments in component classes; fourth, every newly added subscribe in code reviews should be able to answer 'when and by whom does this Observable end'. If it cannot be answered, default to a lifecycle-aware approach.

Applicable Scenarios and Boundaries

This decision logic is suitable for teams maintaining medium-to-large Angular business applications, especially in scenarios involving long lists, modals, polling, push notifications, and shared state. It focuses not on 'how to write unsubscription', but on 'lifecycle responsibility of subscriptions and callbacks'.

However, this does not apply to every project. In one-off demo pages, short-lived campaign pages, or prototype validation, not unsubscribing manually will not cause perceptible memory issues, and force-fitting takeUntilDestroyed only adds complexity. For subscriptions in a global singleton service (e.g., a root-level Service) that are intended to live as long as the app, there is no need to unsubscribe when a page is destroyed; in fact, you should not call takeUntilDestroyed on them inside the service method, because that might cut off streams other pages are using. Another boundary is a Subject shared across components: if multiple components consume the same data source, the correct approach is to place the subscription in each component and use lifecycle-aware destruction, rather than apply a single 'global unsubscribe' in a shared service that cuts them all off at the same time.

Frequently Asked Questions

In Angular, if I use the async pipe, do I still need to manually unsubscribe?

No. The async pipe automatically unsubscribes when the component is destroyed; just use it for template bindings. The remaining cases where manual handling is needed are when you need to actively control a stream while it is not in a template, or need to get the current value.

Will an HttpClient request cause memory leak if I do not manually unsubscribe?

A normal HttpClient request completes and releases its connection, so it does not cause a memory leak. What you do need to watch for is when the API response is slow and the page has already been closed; the callback will still run, so it is recommended to guard it with takeUntilDestroyed to avoid operating on a destroyed component.

How do I choose between takeUntilDestroyed and manual unsubscribe?

For ordinary async flows in a component, prefer takeUntilDestroyed; only when a subscription must be stopped at a business moment rather than when the component is destroyed should you use manual unsubscribe.

Should a subscription inside a service be canceled manually?

Subscriptions in an app-level singleton service share the app's lifecycle, which usually means no unsubscription is needed. If a service is dynamically created by a component and should be destroyed with it, then make sure its subscriptions also use lifecycle-aware disposal.

Does not unsubscribing always lead to a memory leak?

Not necessarily. It is only when you have an infinite stream and the callback references the component instance that the component is likely to be retained; finite streams will not. However, for consistent maintenance and to avoid asynchronous writebacks, it is better to consolidate with the async pipe or takeUntilDestroyed.


Advice for teams: Add subscription lifecycle checks to your daily review checklist. Before writing any subscribe, ask whether it will end on its own, whether the callback references the component, and whether a lifecycle-aware approach can be used. If you are not certain, use memory snapshots to observe for a while before deciding whether cleanup is needed, instead of blindly copying the rule 'every subscription needs to be unsubscribed' from legacy projects.

Have a similar project in mind?
Contact us for a one-to-one project reference proposal
Obtain Proposal
Interested in this topic?
10-year tech team — reference proposal within 24 hours
Obtain Proposal
Are you ready?
Then reach out to us!
+86-13370032918
Discover more services, feel free to contact us anytime.
Please fill in your requirements
What services would you like us to provide for you?
Your Budget
ct.
Our WeChat
Professional technical solutions
Phone
+86-13370032918 (Manager Jin)
The phone is busy or unavailable; feel free to add me on WeChat.
E-mail
349077570@qq.com
Submitted successfully
Thank you for your trust. We will contact you soon!
Recommended projects for you