Skip to content

Support root: event target in @HostListener #70150

Description

@MillerSvt

Which @angular/* package(s) are relevant/related to the feature request?

core

Description

@HostListener supports global event targets such as document:, window: and body::

@HostListener('document:click', ['$event'])
onClick(event: MouseEvent): void {
    // ...
}

There is currently no equivalent target for the root node of the directive/component host element (hostElement.getRootNode()).

This becomes important for directives and components that can run both in the regular document and inside Shadow DOM. Listening on document is not equivalent to listening on the current root: when an element is rendered inside a ShadowRoot, the natural event scope can be that shadow root rather than the owner document.

Today this requires dropping the declarative @HostListener API and manually resolving the root and managing the listener lifecycle, for example:

private readonly element = inject(ElementRef<HTMLElement>);
private readonly destroyRef = inject(DestroyRef);

constructor() {
    const root = this.element.nativeElement.getRootNode();
    const listener = (event: Event) => this.onClick(event);

    root.addEventListener('click', listener);
    this.destroyRef.onDestroy(() => root.removeEventListener('click', listener));
}

private onClick(event: Event): void {
    // ...
}

Proposed solution

Add a root: event target which resolves to getRootNode() of the host element:

@HostListener('root:click', ['$event'])
onClick(event: MouseEvent): void {
    // ...
}

Conceptually, Angular would resolve root: at directive/component creation time as:

hostElement.getRootNode()

This means:

  • for a regular element in the document, the target is Document;
  • for an element inside Shadow DOM, the target is its ShadowRoot.

It would make listeners naturally follow the DOM tree in which the host is actually rendered and would provide a declarative counterpart to manually calling getRootNode().addEventListener(...).

If the same event-target syntax is shared with template event bindings, supporting (root:click) there as well would also be useful and consistent.

Alternatives considered

1. @HostListener('document:click')

This does not express the same semantics. A reusable directive should not have to know whether its host lives directly in Document or in a ShadowRoot.

2. Manual addEventListener / removeEventListener

This works, but requires resolving getRootNode(), retaining the listener reference, and handling destruction manually. It loses the concise declarative lifecycle management provided by @HostListener.

3. RxJS fromEvent(hostElement.getRootNode(), 'click')

This also works, but similarly requires imperative setup and teardown (takeUntilDestroyed, etc.) for something that otherwise maps naturally to @HostListener.

4. A custom EventManagerPlugin

Applications can implement their own event target syntax, but this is disproportionate for a standard DOM concept (Node.getRootNode()) and makes reusable libraries depend on application-level event manager configuration.

Additional

If this approach is approved, I’d be happy to submit a PR with the implementation and tests.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: coreIssues related to the framework runtimecore: event listenersfeatureLabel used to distinguish feature request from other issues

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions