Events
There is NOT such a thing as stencil events, instead, Stencil encourages the use of DOM events.
However, Stencil does provide an API to specify the events a component can emit, and the events a component listens to. It does so with the Event()
and Listen()
decorators.
Event Decorator
Components can emit data and events using the Event Emitter decorator.
To dispatch Custom DOM events for other components to handle, use the @Event()
decorator.
import { Event, EventEmitter } from '@stencil/core';
...
export class TodoList {
@Event() todoCompleted: EventEmitter<Todo>;
todoCompletedHandler(todo: Todo) {
this.todoCompleted.emit(todo);
}
}
The code above will dispatch a custom DOM event called todoCompleted
.
The Event(opts: EventOptions)
decorator optionally accepts an options object to shape the behavior of dispatched events. The options and defaults are described below.
export interface EventOptions {
/**
* A string custom event name to override the default.
*/
eventName?: string;
/**
* A Boolean indicating whether the event bubbles up through the DOM or not.
*/
bubbles?: boolean;
/**
* A Boolean indicating whether the event is cancelable.
*/
cancelable?: boolean;
/**
* A Boolean value indicating whether or not the event can bubble across the boundary between the shadow DOM and the regular DOM.
*/
composed?: boolean;
}
Example:
import { Event, EventEmitter } from '@stencil/core';
...
export class TodoList {
// Event called 'todoCompleted' that is "composed", "cancellable" and it will bubble up!
@Event({
eventName: 'todoCompleted',
composed: true,
cancelable: true,
bubbles: true,
}) todoCompleted: EventEmitter<Todo>;
todoCompletedHandler(todo: Todo) {
const event = this.todoCompleted.emit(todo);
if(!event.defaultPrevented) {
// if not prevented, do some default handling code
}
}
}
In the case where the Stencil Event
type conflicts with the native web Event
type, there are two possible solutions:
- Import aliasing:
import { Event as StencilEvent, EventEmitter } from '@stencil/core';
@StencilEvent() myEvent: EventEmitter<{value: string, ev: Event}>;
- Namespace the native web
Event
type withglobalThis
:
@Event() myEvent: EventEmitter<{value: string, ev: globalThis.Event}>;
Listen Decorator
The Listen()
decorator is for listening to DOM events, including the ones dispatched from @Events
. The event listeners are automatically added and removed when the component gets added or removed from the DOM.
In the example below, assume that a child component, TodoList
, emits a todoCompleted
event using the EventEmitter
.
import { Listen } from '@stencil/core';
...
export class TodoApp {
@Listen('todoCompleted')
todoCompletedHandler(event: CustomEvent<Todo>) {
console.log('Received the custom todoCompleted event: ', event.detail);
}
}
Listen's options
The @Listen(eventName, opts?: ListenOptions)
includes a second optional argument that can be used to configure how the DOM event listener is attached.
export interface ListenOptions {
target?: 'body' | 'document' | 'window';
capture?: boolean;
passive?: boolean;
}
The available options are target
, capture
and passive
:
target
Handlers can also be registered for an event other than the host itself.
The target
option can be used to change where the event listener is attached, this is useful for listening to application-wide events.
In the example below, we're going to listen for the scroll event, emitted from window
:
@Listen('scroll', { target: 'window' })
handleScroll(ev) {
console.log('the body was scrolled', ev);
}
passive
By default, Stencil uses several heuristics to determine if it must attach a passive
event listener or not. The passive
option can be used to change the default behavior.
Please check out https://developers.google.com/web/updates/2016/06/passive-event-listeners for further information.
capture
Event listener attached with @Listen
does not "capture" by default.
When a event listener is set to "capture", it means the event will be dispatched during the "capture phase".
Check out https://www.quirksmode.org/js/events_order.html for further information.
@Listen('click', { capture: true })
handleClick(ev) {
console.log('click');
}
Keyboard events
For keyboard events, you can use the standard keydown
event in @Listen()
and use event.keyCode
or event.which
to get the key code, or event.key
for the string representation of the key.
@Listen('keydown')
handleKeyDown(ev: KeyboardEvent){
if (ev.key === 'ArrowDown'){
console.log('down arrow pressed')
}
}
More info on event key strings can be found in the w3c spec.
Using events in JSX
Within a stencil compiled application or component you can also bind listeners to events directly in JSX. This works very similar to normal DOM events such as onClick
.
Let's use our TodoList component from above:
import { Event, EventEmitter } from '@stencil/core';
...
export class TodoList {
@Event() todoCompleted: EventEmitter<Todo>;
todoCompletedHandler(todo: Todo) {
this.todoCompleted.emit(todo);
}
}
We can now listen to this event directly on the component in our JSX using the following syntax:
<todo-list onTodoCompleted={ev => this.someMethod(ev)} />
This property is generated automatically and is prefixed with "on". For example, if the event emitted is called todoDeleted
the property will be called onTodoDeleted
:
<todo-list onTodoDeleted={ev => this.someOtherMethod(ev)} />
Listening to events from a non-JSX element
<todo-list></todo-list>
<script>
const todoListElement = document.querySelector('todo-list');
todoListElement.addEventListener('todoCompleted', event => { /* your listener */ })
</script>