Skip to content

Event replay: early event contract listeners on <body> are never removed after hydration #71518

Open

Description

@arturovt

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

core

Is this a regression?

No

Description

When event replay is enabled (provideClientHydration(withEventReplay())), the server adds an inline script to the page:

window.__jsaction_bootstrap(document.body, "ng", ["click"], []);

This script adds listeners to <body>. They store each event in a queue, window._ejsas[appId].q, so Angular can replay events that happen before the app has loaded.

After hydration, Angular replays this queue once and sets window._ejsas[appId] to undefined. From then on, Angular handles events with its own listeners.

But the inline script's <body> listeners are never removed. They keep pushing every event into the same queue, and nothing reads it again. Each entry holds the event and its target element. So:

  • the queue grows with every click, for the whole life of the page;
  • elements removed from the page later (for example, after a route change) can't be garbage collected if they were ever clicked.

Cause

In packages/core/src/hydration/event_replay.ts, initEventReplay() replays the early events and calls clearAppScopedEarlyEventContract(appId), but never calls removeAllAppScopedEventListeners(appId). The same happens in the appRef.onDestroy() callback, for an app destroyed before it becomes stable.

For comparison, EventContract.replayEarlyEvents() in @angular/core/primitives/event-dispatch removes the early listeners right after it replays the events.

Steps to reproduce

  1. Clone the repro and run:
    pnpm install
    pnpm build
    pnpm serve:ssr:event-replay-body-listeners-repro
  2. Open http://localhost:4200.
  3. Click the button, or anywhere on the page, a few times.

Actual: "Early event queue size" goes up by 1 on every click.

Expected: it stays 0 after hydration.

Please provide a link to a minimal reproduction of the bug

https://github.com/arturovt/event-replay-body-listeners-repro

Please provide the exception or error you saw

there's no error, memory grows because every event after hydration is added to `window._ejsas[appId].q` (captured by the early <body> listeners), and nothing reads or clears it

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

Angular           : 22.2.3

Anything else?

Related:

Activity

  1. added this to the needsTriage milestone on Oct 21, 2026
  2. added this to the needsTriage milestone on Oct 21, 2026
  3. added 4 commits that reference this issue on Oct 21, 2026
    8e78909
    1b0897b
    fe0bfea
    882452c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: coreIssues related to the framework runtime

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions