--- title: Use {{on}} Modifier Instead of Event Handler Properties impact: MEDIUM impactDescription: Better performance and clearer event handling tags: performance, events, modifiers, best-practices --- ## Use {{on}} Modifier Instead of Event Handler Properties Always use the `{{on}}` modifier for event handling instead of HTML event handler properties. The `{{on}}` modifier provides better memory management, automatic cleanup, and clearer intent. **Why {{on}} is Better:** - Automatic cleanup when element is removed (prevents memory leaks) - Supports event options (`capture`, `passive`, `once`) - More explicit and searchable in templates **Incorrect (HTML event properties):** ```glimmer-js // app/components/button.gjs import Component from '@glimmer/component'; import { action } from '@ember/object'; export default class Button extends Component { @action handleClick() { console.log('clicked'); } } ``` **Correct ({{on}} modifier):** ```glimmer-js // app/components/button.gjs import Component from '@glimmer/component'; import { action } from '@ember/object'; import { on } from '@ember/modifier'; export default class Button extends Component { @action handleClick() { console.log('clicked'); } } ``` ### Event Options The `{{on}}` modifier supports standard event listener options: ```glimmer-js // app/components/scrollable.gjs import Component from '@glimmer/component'; import { action } from '@ember/object'; import { on } from '@ember/modifier'; export default class Scrollable extends Component { @action handleScroll(event) { console.log('scrolled', event.target.scrollTop); } } ``` **Available options:** - `capture` - Use capture phase instead of bubble phase - `once` - Remove listener after first invocation - `passive` - Indicates handler won't call `preventDefault()` (better scroll performance) ### Handling Multiple Events ```glimmer-js // app/components/input-field.gjs import Component from '@glimmer/component'; import { action } from '@ember/object'; import { on } from '@ember/modifier'; export default class InputField extends Component { @action handleFocus() { console.log('focused'); } @action handleBlur() { console.log('blurred'); } @action handleInput(event) { this.args.onChange?.(event.target.value); } } ``` ### Preventing Default and Stopping Propagation Handle these in your action, not in the template: ```glimmer-js // app/components/form.gjs import Component from '@glimmer/component'; import { action } from '@ember/object'; import { on } from '@ember/modifier'; export default class Form extends Component { @action handleSubmit(event) { event.preventDefault(); // Prevent page reload event.stopPropagation(); // Stop event bubbling if needed this.args.onSubmit?.(/* form data */); } } ``` ### Keyboard Events ```glimmer-js // app/components/keyboard-nav.gjs import Component from '@glimmer/component'; import { action } from '@ember/object'; import { on } from '@ember/modifier'; export default class KeyboardNav extends Component { @action handleKeyDown(event) { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); this.args.onActivate?.(); } if (event.key === 'Escape') { this.args.onCancel?.(); } } } ``` ### Performance Tip: Event Delegation For lists with many items, use event delegation on the parent: ```glimmer-js // app/components/todo-list.gjs import Component from '@glimmer/component'; import { action } from '@ember/object'; import { on } from '@ember/modifier'; export default class TodoList extends Component { @action handleClick(event) { // Find which todo was clicked const todoId = event.target.closest('[data-todo-id]')?.dataset.todoId; if (todoId) { this.args.onTodoClick?.(todoId); } } } ``` ### Common Pitfalls **❌ Don't bind directly without @action:** ```glimmer-js // This won't work - loses 'this' context ``` **✅ Use @action decorator:** ```glimmer-js @action myMethod() { // 'this' is correctly bound } ``` **❌ Don't use string event handlers:** ```glimmer-js {{! Security risk and doesn't work in strict mode }} ``` Always use the `{{on}}` modifier for cleaner, safer, and more performant event handling in Ember applications. **References:** - [Ember Modifiers Guide](https://guides.emberjs.com/release/components/template-lifecycle-dom-and-modifiers/) - [{{on}} Modifier RFC](https://github.com/emberjs/rfcs/blob/master/text/0471-on-modifier.md) - [Event Listener Options](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#parameters)