Two small Angular directives:
handleEvent: event delegation. One handler for many elements, matched by CSS selectors. It also works for HTML injected withinnerHTML, for example links in CMS content.outsideEvent: click outside an element, with a list of elements to ignore.
Live demo on StackBlitz · Changes
npm install ngx-event-handler| ngx-event-handler | Angular |
|---|---|
| 23.x | 22.x |
| 18.x | 18.x |
23.0.0 has a new API. See the changelog when upgrading.
Import the directives in a standalone component, or import NgxEventHandlerModule in an NgModule.
import { HandleEventDirective, OutsideEventDirective } from 'ngx-event-handler';
@Component({
imports: [HandleEventDirective, OutsideEventDirective],
// ...
})
export class MyComponent {}Handle clicks on links inside HTML you don't control:
<div [innerHTML]="html" (handleEvent)="linkClicked($event)" [handleEventInclusion]="['a']"></div>linkClicked({ event, element }: HandledEvent) {
event.preventDefault();
this.router.navigateByUrl((element as HTMLAnchorElement).pathname);
}<button id="openButton" (click)="open = true">Open</button>
<div class="popup" (outsideEvent)="open = false" [outsideEventExclusion]="['#openButton']">...</div>A drag that starts inside the element and ends outside it (for example selecting text) doesn't count as an outside click.
For popups and menus, consider the native
popoverattribute or Angular CDK Overlay. They also handle Escape and focus.outsideEventis for the cases they don't cover, such as inline editors and selections.
The demo shows real-world patterns, each with its code:
| Example | Directive | Pattern |
|---|---|---|
| Account menu | outsideEvent |
Close a dropdown on outside click; wrap trigger and menu so no exclusion is needed. |
| Inline editing | outsideEvent |
Save on outside click, cancel on Escape; selecting text past the input's edge keeps it open. |
| Router links in CMS content | handleEvent |
Route internal links in innerHTML content with preventDefault(); external links and Ctrl/Cmd-clicks keep working. |
| Row actions in a long list | handleEvent |
One listener for every row and button, using data-action attributes. |
| Selection with an action bar | both | Multi-select files, clear on outside click, but not when clicking the fixed action bar. |
Inclusion and exclusion lists contain CSS selectors, for example ['#toolbar', 'button', '.selected'].
An element matches when it, or one of its parents, matches one of the selectors.
| Input | Default | Description |
|---|---|---|
handleEventInclusion |
[] |
Only emit for events on matching elements. The matching element is emitted. When empty, every element is included. |
handleEventExclusion |
[] |
Ignore events on matching elements. |
handleEventType |
'click' |
The event to listen to. |
handleEventTarget |
'host' |
Where to listen: 'host', 'document' or 'window'. With document or window, events outside the host element are matched too. |
handleEventDelay |
0 |
Delay in ms before emitting. With a delay, preventDefault() has no effect. |
Output handleEvent: HandledEvent, where HandledEvent is { event: Event; element: Element }. It is emitted synchronously when there is no delay.
| Input | Default | Description |
|---|---|---|
outsideEventExclusion |
[] |
Clicks on matching elements don't trigger outsideEvent. |
outsideEventDelay |
0 |
Delay in ms before emitting. |
Output outsideEvent: MouseEvent: emitted for clicks outside the host element and outside the excluded elements.
Both directives can be used on the same element. For example, to select SVG nodes and deselect on any other click:
<div (handleEvent)="deselect()" [handleEventExclusion]="['g']" (outsideEvent)="deselect()">
<svg (handleEvent)="select($event.element)" [handleEventInclusion]="['g']">...</svg>
</div>npm start # demo on http://localhost:4202
npm test # unit tests (Vitest)
npm run build # library build to ./package