Skip to content

HostListener/output() memory leak in SSR #66506

@mii-shell

Description

@mii-shell

Which @angular/* package(s) are the source of the bug?

Don't known / other

Is this a regression?

No

Description

It appears that elements are not being garbage collected in an SSR application if:

  • A child component contains an output() which it's parent is using
  • A component is using a @HostListener

How to reproduce in provided minimal sample

  • Either start the dev server or run a production build (both have the same result)
  • Open the app
  • Open dev tools
  • Click the toggle button multiple times
  • Take a snapshot of the heap
  • You now see multiple detached instances of both components which are still held in memory even though not used anymore
Image

Validation

  • Remove @HostListener from WithHostListener component
    // Change this
    @HostListener('click', ['$event'])
    public onClick(event: MouseEvent): void {}
    
    // to this
    public onClick(event: MouseEvent): void {}
  • Remove WithOutput component's output listener from App component
    <!-- change this -->
    <app-with-output (myOutput)="doSomething()"></app-with-output>
    
    <!-- to this -->
    <app-with-output></app-with-output>
  • Run the app
  • Click the toggle button multiple times
  • Take a heap snapshot
  • Now you can see that the DOM Nodes are cleaned up correctly
Image

Changing the WithHostListener component to cleaning up the click listener manually (as seen in the snippet below) also enables the GC to cleanup the app-with-host-listener nodes.

import { Component, ElementRef, inject, OnDestroy, Renderer2 } from '@angular/core';

@Component({
  selector: 'app-with-host-listener',
  template: `
    <div>With HostListener</div>
  `,
})
export class WithHostListener implements OnDestroy {
  private readonly renderer = inject(Renderer2);
  private cleanupListener: (() => void) | undefined;
  private readonly el = inject(ElementRef);

  constructor() {
    this.cleanupListener = this.renderer.listen(this.el.nativeElement, 'click', event => {
			this.onClick(event);
		});
  }

	public onClick(event: MouseEvent): void {console.log(event)}

  ngOnDestroy(): void {
		if (this.cleanupListener) {
			this.cleanupListener();
		}
  }
}

Please provide a link to a minimal reproduction of the bug

https://github.com/mii-shell/angular-dom-nodes-memory-leak

Please provide the exception or error you saw

No exception/error was thrown

Please provide the environment you discovered this bug in (run ng version)

Angular CLI       : 21.0.5
Angular           : 21.1.0-rc.0
Node.js           : 24.4.1
Package Manager   : npm 11.4.2
Operating System  : linux x64

┌───────────────────────────┬───────────────────┬───────────────────┐
│ Package                   │ Installed Version │ Requested Version │
├───────────────────────────┼───────────────────┼───────────────────┤
│ @angular/build            │ 21.1.0-rc.0       │ ^21.1.0-rc.0      │
│ @angular/cli              │ 21.0.5            │ ^21.0.5           │
│ @angular/common           │ 21.1.0-rc.0       │ ^21.1.0-rc.0      │
│ @angular/compiler         │ 21.1.0-rc.0       │ ^21.1.0-rc.0      │
│ @angular/compiler-cli     │ 21.1.0-rc.0       │ ^21.1.0-rc.0      │
│ @angular/core             │ 21.1.0-rc.0       │ ^21.1.0-rc.0      │
│ @angular/forms            │ 21.1.0-rc.0       │ ^21.1.0-rc.0      │
│ @angular/platform-browser │ 21.1.0-rc.0       │ ^21.1.0-rc.0      │
│ @angular/platform-server  │ 21.1.0-rc.0       │ ^21.1.0-rc.0      │
│ @angular/router           │ 21.1.0-rc.0       │ ^21.1.0-rc.0      │
│ @angular/ssr              │ 21.1.0-rc.0       │ ^21.1.0-rc.0      │
│ rxjs                      │ 7.8.2             │ ~7.8.0            │
│ typescript                │ 5.9.3             │ ~5.9.2            │
│ vitest                    │ 4.0.17            │ ^4.0.8            │
└───────────────────────────┴───────────────────┴───────────────────┘

Anything else?

  • I originally thought this problem might be related to Event replay memory leak #59261, which is why I've used Angular version 21.1.0-rc.0 in my minimal reproduction (which is the version in which this was fixed according to the changelog). Unfortunately, it did not resolve the issue.
  • I've also tested it with version 21.0.5, which resulted in the same
  • Manual garbage collection does not remove the elements from the heap either
  • Identical issue, regardless of whether @HostListener or host option in @Component is used
  • This issue does not occur in a basic Angular app setup without SSR

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions