{"record":{"id":"d27a02efb83228eb","repo":"angular/angular","slug":"composedpath-called-during-event-replay","errorCode":null,"errorMessage":"`composedPath` called during event replay.","messagePattern":"`composedPath` called during event replay\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/primitives/event-dispatch/src/event_dispatcher.ts","lineNumber":134,"sourceCode":"function propagationStopped(eventInfoWrapper: EventInfoWrapper) {\n  const event = eventInfoWrapper.getEvent();\n  return !!event[PROPAGATION_STOPPED_SYMBOL];\n}\n\nfunction prepareEventForReplay(eventInfoWrapper: EventInfoWrapper) {\n  const event = eventInfoWrapper.getEvent();\n  const target = eventInfoWrapper.getTargetElement();\n  const originalPreventDefault = event.preventDefault.bind(event);\n  patchEventInstance(event, 'target', target);\n  patchEventInstance(event, 'eventPhase', EventPhase.REPLAY);\n  patchEventInstance(event, 'preventDefault', () => {\n    originalPreventDefault();\n    throw new Error(\n      PREVENT_DEFAULT_ERROR_MESSAGE + (ngDevMode ? PREVENT_DEFAULT_ERROR_MESSAGE_DETAILS : ''),\n    );\n  });\n  patchEventInstance(event, 'composedPath', () => {\n    throw new Error(\n      COMPOSED_PATH_ERROR_MESSAGE + (ngDevMode ? COMPOSED_PATH_ERROR_MESSAGE_DETAILS : ''),\n    );\n  });\n}\n\nfunction prepareEventForDispatch(eventInfoWrapper: EventInfoWrapper) {\n  const event = eventInfoWrapper.getEvent();\n  const currentTarget = eventInfoWrapper.getAction()?.element;\n  if (currentTarget) {\n    patchEventInstance(event, 'currentTarget', currentTarget, {\n      // `currentTarget` is going to get reassigned every dispatch.\n      configurable: true,\n    });\n  }\n}\n\n/**\n * Patch `Event` instance during non-standard `Event` dispatch. This patches just the `Event`","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/angular/angular/blob/51cb07e98081ab7e4e84a9e0949266a8e19cca84/packages/core/primitives/event-dispatch/src/event_dispatcher.ts#L116-L152","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst row = event.composedPath().find((n) => n instanceof HTMLElement && n.tagName === 'TR'); // throws during replay\n\n// after\nlet node: Node | null = event.target as Node;\nlet row: HTMLElement | null = null;\nwhile (node) {\n  if (node instanceof HTMLElement && node.tagName === 'TR') { row = node; break; }\n  node = node.parentElement;\n}","handlingStrategy":"type-guard","validationCode":"import {EventPhase} from '@angular/core/primitives/event-dispatch';\n\nconst isReplay = (event: Event): boolean => event.eventPhase === EventPhase.REPLAY;\n\nfunction findAncestor(event: Event, match: (el: HTMLElement) => boolean): HTMLElement | null {\n  let node = event.target instanceof HTMLElement ? event.target : null;\n  while (node) {\n    if (match(node)) return node;\n    node = node.parentElement;\n  }\n  return null;\n}","typeGuard":"import {EventPhase} from '@angular/core/primitives/event-dispatch';\n\nconst isReplayed = (e: Event): boolean => e.eventPhase === EventPhase.REPLAY;\n\nconst path = (e: Event): Node[] =>\n  isReplayed(e) ? walkFrom(e.target) : (e.composedPath() as Node[]);","tryCatchPattern":null,"preventionTips":["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"],"tags":["event-dispatch","replay","composed-path","primitives"],"backgroundTag":"event-replay-restricted-api","analyzedSha":"51cb07e98081ab7e4e84a9e0949266a8e19cca84","analyzedAt":"2026-08-22T07:04:54.531Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}