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
- Replace composedPath() usage with a manual walk from event.target / event.currentTarget upward: iterate node.parentElement until the desired ancestor or boundary is found
- Use element.closest(selector) on event.target for ancestor lookup — it works identically during replay
- 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
- Prefer element.closest() or parent walks over composedPath() in delegated handlers
- Treat composedPath() as unavailable during replay; design handlers around target/currentTarget
- Centralize event-path lookups in one utility that handles both replay and native phases
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.