angular/angular · error · Error

`composedPath` called during event replay.

Error message

`composedPath` called during event replay.

What it means

During event replay, the EventDispatcher (@angular/core/primitives/event-dispatch) patches composedPath() to throw, because replay happens after the browser dispatch and the event's composed path is no longer meaningful — it would return an empty/incorrect list. The patched event instead exposes correct `target` and, per dispatch step, `currentTarget`, and the error message recommends iterating parent nodes from those if you need the event path.

Source

Thrown at packages/core/primitives/event-dispatch/src/event_dispatcher.ts:134

function propagationStopped(eventInfoWrapper: EventInfoWrapper) {
  const event = eventInfoWrapper.getEvent();
  return !!event[PROPAGATION_STOPPED_SYMBOL];
}

function prepareEventForReplay(eventInfoWrapper: EventInfoWrapper) {
  const event = eventInfoWrapper.getEvent();
  const target = eventInfoWrapper.getTargetElement();
  const originalPreventDefault = event.preventDefault.bind(event);
  patchEventInstance(event, 'target', target);
  patchEventInstance(event, 'eventPhase', EventPhase.REPLAY);
  patchEventInstance(event, 'preventDefault', () => {
    originalPreventDefault();
    throw new Error(
      PREVENT_DEFAULT_ERROR_MESSAGE + (ngDevMode ? PREVENT_DEFAULT_ERROR_MESSAGE_DETAILS : ''),
    );
  });
  patchEventInstance(event, 'composedPath', () => {
    throw new Error(
      COMPOSED_PATH_ERROR_MESSAGE + (ngDevMode ? COMPOSED_PATH_ERROR_MESSAGE_DETAILS : ''),
    );
  });
}

function prepareEventForDispatch(eventInfoWrapper: EventInfoWrapper) {
  const event = eventInfoWrapper.getEvent();
  const currentTarget = eventInfoWrapper.getAction()?.element;
  if (currentTarget) {
    patchEventInstance(event, 'currentTarget', currentTarget, {
      // `currentTarget` is going to get reassigned every dispatch.
      configurable: true,
    });
  }
}

/**
 * Patch `Event` instance during non-standard `Event` dispatch. This patches just the `Event`

View on GitHub (pinned to 51cb07e980)

Solutions

  1. Replace composedPath() usage with a manual walk from event.target / event.currentTarget upward: iterate node.parentElement until the desired ancestor or boundary is found
  2. Use element.closest(selector) on event.target for ancestor lookup — it works identically during replay
  3. As with other replay-restricted APIs, gate the branch on event.eventPhase === EventPhase.REPLAY if you must keep composedPath() for the native path

Example fix

// before
const row = event.composedPath().find((n) => n instanceof HTMLElement && n.tagName === 'TR'); // throws during replay

// after
let node: Node | null = event.target as Node;
let row: HTMLElement | null = null;
while (node) {
  if (node instanceof HTMLElement && node.tagName === 'TR') { row = node; break; }
  node = node.parentElement;
}
Defensive patterns

Strategy: type-guard

Validate before calling

import {EventPhase} from '@angular/core/primitives/event-dispatch';

const isReplay = (event: Event): boolean => event.eventPhase === EventPhase.REPLAY;

function findAncestor(event: Event, match: (el: HTMLElement) => boolean): HTMLElement | null {
  let node = event.target instanceof HTMLElement ? event.target : null;
  while (node) {
    if (match(node)) return node;
    node = node.parentElement;
  }
  return null;
}

Type guard

import {EventPhase} from '@angular/core/primitives/event-dispatch';

const isReplayed = (e: Event): boolean => e.eventPhase === EventPhase.REPLAY;

const path = (e: Event): Node[] =>
  isReplayed(e) ? walkFrom(e.target) : (e.composedPath() as Node[]);

Prevention

When it happens

Trigger: A replayed event's handler calls event.composedPath(), typically in event-delegation code that uses composedPath() to find an ancestor (e.g., closest matching list item) or to check whether the listener target is in the path.

Common situations: Delegated event handling utilities (finding the originating element via composedPath) reused with the event-dispatch primitive; hydration-time events replayed after bootstrap hitting shared handler code.

Related errors


AI-assisted analysis of angular/angular@51cb07e980 (2026-08-22). Data as JSON: /api/errors/d27a02efb83228eb. Report an issue: GitHub.