Listener
Reactive event listener with automatic cleanup. Attach event handlers to DOM elements, window, or document with automatic removal on component destroy.
Prefer Angular's built-in event listeners
For most event handling scenarios, Angular's built-in event binding syntax is recommended:
- Template event listeners: use
(event)="handler()"in component templates for declarative event handling - Host element events: use the
hostproperty in the@Componentdecorator to listen to events on the host element
@Component({
template: `<button (click)="handleClick()">Click</button>`
})
export class MyComponent {
handleClick() { /* ... */ }
}Usage
import { Component, viewChild, ElementRef } from '@angular/core';
import { listener } from '@signality/core';
@Component({
template: `<button #btn>Click me</button>`,
})
export class ClickTracker {
readonly btn = viewChild<ElementRef>('btn');
constructor() {
listener(this.btn, 'click', event => {
console.log('Button clicked!', event);
});
}
}When this can be useful
The listener utility is primarily designed for internal composition within other Signality utilities. However, it can be useful in application code in the following scenarios:
- Event modifiers not available in templates: When you need declarative modifiers like
capture,passive,once,stop,prevent, orselfthat aren't supported by Angular's template syntax:
// Passive listener for better performance
listener.passive(window, 'wheel', () => {
// Smooth scrolling without blocking
});
// Capture phase listener + once option
listener.capture.once(element, 'click', () => {
// Handles click in capture phase and once
});- Dynamic event names: When the event name itself is reactive and changes over time:
const eventType = signal<'click' | 'mouseenter'>('click');
listener(element, eventType, handler); // Re-attaches when eventType changes- Manual lifecycle management: When you need to optimize performance by conditionally registering listeners or destroying them when they're no longer needed:
let listenerRef: ListenerRef | null = null;
startListening() {
// Register listener only when needed
listenerRef = listener(window, 'resize', handleResize);
}
stopListening() {
// Destroy listener when not needed
listenerRef?.destroy();
listenerRef = null;
}Parameters
| Parameter | Type | Description |
|---|---|---|
target | MaybeElementSignal<T> | Target element, window, or document |
event | MaybeSignal<string> | Event name to listener for |
handler | (event) => void | Event handler function |
options | ListenerOptions | Optional configuration (see Options below) |
Options
The ListenerOptions extends WithInjector:
| Option | Type | Description |
|---|---|---|
injector | Injector | Optional injector for DI context |
All other configuration (capture, passive, once, stop, prevent, self) is done through modifiers.
Modifiers
Event listener configuration is done through modifiers:
// Use modifiers for configuration:
listener.capture.passive(element, 'click', handler);Available modifiers:
listener.capture(...)- equivalent to{ capture: true }listener.passive(...)- equivalent to{ passive: true }listener.once(...)- equivalent to{ once: true }listener.stop(...)- callsevent.stopPropagation()listener.prevent(...)- callsevent.preventDefault()listener.self(...)- only triggers if event originated from the element itself
Modifiers can be chained in any order:
listener.capture.passive.once(element, 'wheel', handler);
listener.stop.prevent(element, 'submit', handler);
listener.self.stop(element, 'click', handler);Return Value
Returns a ListenerRef (alias for EffectRef) that can be used to manually destroy the listenerer.
Examples
Dynamic event name
import { Component, signal, viewChild, ElementRef } from '@angular/core';
import { listener } from '@signality/core';
@Component({
template: `
<div #target>Hover or click me</div>
<button (click)="toggleEvent()">Toggle event</button>
<p>Event count: {{ count() }}</p>
`,
})
export class DynamicEvent {
readonly target = viewChild<ElementRef>('target');
readonly eventName = signal<'click' | 'mouseenter'>('click');
readonly count = signal(0);
constructor() {
// Re-attaches when eventName changes
listener(this.target, this.eventName, () => {
this.count.update(c => c + 1);
});
}
toggleEvent() {
this.eventName.update(e => e === 'click' ? 'mouseenter' : 'click');
}
}Modifiers
import { Component, viewChild, ElementRef } from '@angular/core';
import { listener } from '@signality/core';
@Component({
template: `
<form #form>
<button type="submit">Submit</button>
</form>
`,
})
export class ModifiersExample {
readonly form = viewChild<ElementRef>('form');
constructor() {
// Stop event propagation
listener.stop(this.form, 'click', e => {
console.log('Form clicked, propagation stopped');
});
// Prevent default and stop propagation
listener.prevent.stop(this.form, 'submit', e => {
console.log('Form submit prevented');
});
// Only trigger if clicked directly on form (not children)
listener.self(this.form, 'click', e => {
console.log('Form itself was clicked');
});
// Chain multiple modifiers
listener.capture.passive.once(this.form, 'wheel', e => {
console.log('Wheel captured once');
});
}
}Manual cleanup
Listeners are automatically unregistered after the view is destroyed (see Automatic cleanup). However, you can also manually destroy them if needed:
import { Component, viewChild, ElementRef, signal } from '@angular/core';
import { listener } from '@signality/core';
@Component({
template: `
<div #target>Move mouse here</div>
<button (click)="stopTracking()">Stop tracking</button>
`,
})
export class ManualCleanup {
readonly target = viewChild<ElementRef>('target');
readonly position = signal({ x: 0, y: 0 });
readonly listener = listener(this.target, 'mousemove', e => {
this.position.set({ x: e.clientX, y: e.clientY });
});
stopTracking() {
this.listener.destroy();
}
}Type Definitions
type ListenerOptions = WithInjector;
export interface ListenerRef {
readonly destroy: () => void;
}
// Window events
function listener<E extends keyof WindowEventMap>(
target: Window,
event: MaybeSignal<E>,
handler: (e: WindowEventMap[E]) => any,
options?: ListenerOptions
): ListenerRef;
// Document events
function listener<E extends keyof DocumentEventMap>(
target: Document,
event: MaybeSignal<E>,
handler: (e: DocumentEventMap[E]) => any,
options?: ListenerOptions
): ListenerRef;
// Element events
function listener<T extends HTMLElement, E extends keyof HTMLElementEventMap>(
target: MaybeElementSignal<T>,
event: MaybeSignal<E>,
handler: (e: HTMLElementEventMap[E]) => any,
options?: ListenerOptions
): ListenerRef;
// SVGElement events
function listener<T extends SVGElement, E extends keyof SVGElementEventMap>(
target: MaybeElementSignal<T>,
event: MaybeSignal<E>,
handler: (e: SVGElementEventMap[E]) => any,
options?: ListenerOptions
): ListenerRef;
// Generic events
function listener<EventType = Event>(
target: MaybeElementSignal<HTMLElement>,
event: MaybeSignal<string>,
handler: (e: EventType) => void,
options?: ListenerOptions
): ListenerRef;Related
- ElementHover — Track hover state
- ElementFocus — Track focus state
- MousePosition — Track mouse position