Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

ngOnDestroy Not Working in ComponentPortal?

Facing issues with ngOnDestroy in ComponentPortal? Learn why it fails and how to properly trigger component destruction in Angular overlays.
Angular developer confused why ngOnDestroy is not called in ComponentPortal, surrounded by ghost components and memory leak warnings Angular developer confused why ngOnDestroy is not called in ComponentPortal, surrounded by ghost components and memory leak warnings
  • 🧩 ngOnDestroy() won't auto-trigger for components created with ComponentPortal.
  • 💾 Forgetting ref.destroy() can lead to severe memory leaks in Angular apps.
  • ⚠️ Overlays using ComponentPortal remove DOM but not the component instance.
  • 🔍 DevTools memory profiling reveals retained components not being destroyed.
  • 🛠️ Wrapping ComponentPortal logic in services ensures safer lifecycle management.

The Lifecycle Gap in Angular Portals

If you use Angular's CDK and ComponentPortal to show components, you might have noticed a problem. The ngOnDestroy() lifecycle hook doesn’t get called when you expect it to. This can cause memory leaks and performance issues. Nobody wants that. This article tells you why this happens. It also explains how Angular manages component lifecycles with portals, and how you can make sure your components get cleaned up.


Understanding the Role of ngOnDestroy in Angular

Angular gives you lifecycle hooks. These let you connect to certain points in a component's life: when it is made, updated, or removed. ngOnDestroy() is one of the most important hooks.

Angular calls this method when it is about to remove a component or directive from the DOM. It also calls it when it destroys the component's instance. People commonly use it to:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

  • ✅ Stop listening to observables. This prevents endless next() calls.
  • ✅ Take away event listeners from the browser or DOM.
  • ✅ Clear setInterval or setTimeout timers.
  • ✅ Run shutdown steps for services or other libraries.
  • ✅ Get rid of large memory items, like canvas elements or maps.

Normally, Angular does this on its own. It happens when you use structural directives like *ngIf or routing. But things are very different when you create a component outside of Angular’s normal component structure. This happens with ComponentPortal, for example.


What is ComponentPortal?

ComponentPortal is a class from the Angular Component Dev Kit (CDK). It helps you use methods where you need to show components programmatically. You can attach a component to a PortalOutlet when your app is running. This means your code decides what to show and where to show it at that moment.

Here are four common ways to use it:

  1. Modals – Dialogs that look different based on what a user types.
  2. Tooltips and Popovers – UI items that float or appear next to other controls.
  3. Dropdowns and Menus – Interaction items with many parts.
  4. Dashboards and Widgets – Screens you can build with movable sections while the app runs.

Basic Code Example

const portal = new ComponentPortal(MyComponent);
const ref = portalOutlet.attach(portal);

When you run this code:

  • MyComponent gets created.
  • It goes into the PortalOutlet you gave it. This could be an overlay or a container.
  • You then get a ComponentRef<MyComponent>. This points to the component that is now active.

At this point, the component shows up. But it is not clear that nothing will remove it if you forget ref.destroy().


Why ngOnDestroy() Doesn’t Fire Automatically

In Angular’s normal way of working, the framework creates and removes components. It does this through routers, container components, or structural directives like *ngIf. In these cases, Angular follows the component's lifecycle. And it makes sure everything is taken down, including calling ngOnDestroy().

But when you use ComponentPortal, the framework puts most of the cleanup work on you, the developer:

  • Angular makes the component instance by hand using the injector.
  • The component is not connected to Angular’s usual change detection and lifecycle system.
  • Lifecycle hooks like ngOnDestroy() do NOT get connected by themselves when the component leaves the DOM.

So, if you do not clearly call ref.destroy() on the ComponentRef, the ngOnDestroy() method of your component will never run. This causes your app to hold onto resources.


Component Lifecycles and Component Showing Realities

When you use ComponentPortal to make and put in a component, Angular gives you a ComponentRef<T>. This object is very useful and lets you:

  • An actual instance of the component: ref.instance
  • The host element in the DOM: ref.location.nativeElement
  • Access to change detection: ref.changeDetectorRef
  • Manual lifecycle controls: ref.destroy()

Proper Lifecycle Flow with ComponentPortal

const portal = new ComponentPortal(MyComponent);
const ref = portalOutlet.attach(portal); // Component initialized and rendered
// ...
ref.destroy(); // Properly triggers ngOnDestroy()

If you skip ref.destroy(), it means:

  • Subscriptions stay active.
  • Your component still uses memory.
  • DOM elements might be gone, but the component instances can still be there and leak memory.

Why Overlays and ComponentPortal Need Clear Destruction

The Angular CDK Overlay system often shows this problem. This is a common way to use ComponentPortal. When an overlay closes, it usually takes away the DOM node. But it does not take away the Angular component itself that you put there.

This difference can cause several unexpected issues:

  • 🔄 Subscriptions in components might keep sending out data. This can cause bugs.
  • 📌 Event listeners, like window.addEventListener(), do not get removed.
  • 💥 You might see a 30–50% rise in memory use over time if overlays are not managed.
  • 🧪 Components that are no longer linked can cause bugs that are hard to find.

Example Use Case: Tooltip Leak

const portal = new ComponentPortal(TooltipComponent);
const ref = overlayRef.attach(portal);

// Overlay gets removed visually
overlayRef.detach(); // Just removes the DOM, NOT the component

// TooltipComponent is still alive in JS memory

This way of doing things is common, but it is risky. Developers think removing the DOM node means the component is fully gone. But this is not true for Angular's CDK.


Fix: Always Call .destroy() on the ComponentRef

No matter if you use overlays, containers, or dashboard widgets, always keep the reference to your ComponentRef. And make sure you get rid of it carefully.

Correct Pattern

const portal = new ComponentPortal(MyComponent);
const ref = portalOutlet.attach(portal);

// Later during teardown
ref.destroy(); // ngOnDestroy() will be called here

This one method call makes sure:

  • The component's teardown steps run.
  • Observables, listeners, and timers are removed.
  • Angular's garbage collector can free up memory.

Centralized Lifecycle Management

People make mistakes. And it is easy to forget to destroy components when you work with many UIs. What is the best way to handle this? Put all this logic in one place.

Sample Service for Portal Lifecycle Management

@Injectable({ providedIn: 'root' })
export class PortalService {
  private refs: ComponentRef<any>[] = [];

  attachComponent<T>(component: Type<T>, outlet: PortalOutlet): ComponentRef<T> {
    const portal = new ComponentPortal(component);
    const ref = outlet.attach(portal);
    this.refs.push(ref);
    return ref;
  }

  destroyAll() {
    this.refs.forEach(ref => ref.destroy());
    this.refs = [];
  }
}

This service does these things:

  • ✅ It attaches and keeps track of components.
  • ✅ It destroys many components during teardown.
  • ✅ It makes sure component lifecycles are the same throughout the app.

Apply destroyAll() during navigation, modal closing, tab switching, or other teardown events.


Safer Alternatives to ComponentPortal

While ComponentPortal is useful, sometimes you do not need it. Think about if simpler Angular tools can fix your problem without making things too hard.

Substitutes:

  • Use routing: Let Angular handle views and their lifecycles.
  • Use *ngIf/*ngFor: Show or hide content as needed. Angular will handle their lifecycles.
  • Use Angular Material modules: Their dialogs, overlays, and layout components know about lifecycles. They use ComponentPortal methods behind the scenes.
  • Use ngComponentOutlet: To load components in templates as needed. This includes binding and destroy lifecycles.

Only use ComponentPortal when:

  • Your components need to be separate from their surroundings.
  • You must have full control over how things look (like absolute positioning or overlays).
  • You are putting together parts of a screen as needed (like dashboards with widgets).

Testing ngOnDestroy() and Memory Cleanup

Never assume components are cleaned up the right way. Always test them. Here are some ways to do that:

Log from ngOnDestroy

ngOnDestroy() {
  console.log('Component destroyed');
}

Check browser logs when you do things like navigating or closing a modal. This will confirm it.

Use Chrome DevTools for Memory Profiling

  1. Open Performance tab
  2. Record a session that includes component creation and teardown
  3. Inspect the memory/references panel
  4. Search for still-alive instances of your component

If you see components still there, it means you forgot to call destroy().

Unit Testing Example

it('should destroy dynamic component on teardown', () => {
  const portal = new ComponentPortal(MyComponent);
  const ref = outlet.attach(portal);
  spyOn(ref.instance, 'ngOnDestroy' as any);

  ref.destroy();
  expect(ref.instance.ngOnDestroy).toHaveBeenCalled();
});

You can also automate memory checks. Use headless testing tools like Cypress with performance plugins, or Puppeteer benchmarks.


Risks of Ignoring the Issue

If you do not clean up components that are made as needed, you face big, ongoing problems:

  • ❌ Memory leaks, especially in apps that run for a long time.
  • ❌ Event handlers that go off when you do not expect them, like during navigation or after modals close.
  • ❌ More component error logs and odd bugs that are hard to fix.
  • ❌ Bigger app size and slower performance, especially on less powerful devices.

Some reports and tests show that memory leaks from portals you do not destroy can make Angular apps that use many dashboards use 20–70% more memory.


Real-World Use Cases & Challenges

Here are some real scenarios where proper ComponentPortal handling makes a difference:

Dashboards

  • Many components loaded based on what the user sets up.
  • Lifecycle steps that are hard to guess.
  • Users might not "unload" components unless you control it with code.
  • Opening and closing modals quickly makes component lifetimes jump.
  • Some modals can hold 3–5 components.
  • Leaking these makes memory use much, much higher.

Marketing Widgets & Custom Forms

  • Added to the DOM as needed through a main setup or tools that change content for users.
  • Often removed from the view but never destroyed. This means the DOM node stays there without you being able to control it.

In each case, the answer is: Keep the ComponentRef. And make sure you destroy it.


Best Practices Checklist

  • ✅ Always keep and manage your ComponentRef.
  • ✅ Always call ref.destroy() during teardown.
  • ✅ Put portal logic and clean-up inside special services.
  • ✅ Do not use ComponentPortal if normal Angular ways work.
  • ✅ Test lifecycles and use DevTools to check if ngOnDestroy() runs.
  • ✅ Look at memory use when you test overlays and widgets.
  • ✅ Do not trust what you see (DOM removal) to mean the component is gone.

Further Reading and Resources


Build With Confidence

At Devsolus, we want to help Angular developers avoid tricky problems like this. Then you can build good, fast apps without worrying about hidden memory leaks. Once you better understand how Angular handles lifecycles for things created on the fly, and use some helpful methods, you will be able to handle your overlays and components well.


Citations

Angular CDK Docs. (n.d.). Retrieved from https://angular.io/cdk/portal/overview

Angular CDK API. (n.d.). Retrieved from https://angular.io/api/cdk/portal/ComponentPortal

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading