Files
marco/.agents/skills/ember-best-practices/rules/component-on-modifier.md

2.8 KiB

title, impact, impactDescription, tags
title impact impactDescription tags
Use {{on}} Modifier for Event Handling MEDIUM Better memory management and clarity events, modifiers, on, performance

Use {{on}} Modifier for Event Handling

Use the {{on}} modifier for event handling instead of traditional action handlers for better memory management and clearer code.

Incorrect (traditional action attribute):

// app/components/button.gjs
import Component from '@glimmer/component';
import { action } from '@ember/object';

class Button extends Component {
  @action
  handleClick() {
    this.args.onClick?.();
  }

  <template>
    <button onclick={{this.handleClick}}>
      {{@label}}
    </button>
  </template>
}

Correct (using {{on}} modifier):

// app/components/button.gjs
import Component from '@glimmer/component';
import { on } from '@ember/modifier';

class Button extends Component {
  handleClick = () => {
    this.args.onClick?.();
  };

  <template>
    <button {{on "click" this.handleClick}}>
      {{@label}}
    </button>
  </template>
}

With event options:

// app/components/scroll-tracker.gjs
import Component from '@glimmer/component';
import { on } from '@ember/modifier';

class ScrollTracker extends Component {
  handleScroll = (event) => {
    console.log('Scroll position:', event.target.scrollTop);
  };

  <template>
    <div class="scrollable" {{on "scroll" this.handleScroll passive=true}}>
      {{yield}}
    </div>
  </template>
}

Multiple event handlers:

// app/components/input-field.gjs
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { on } from '@ember/modifier';

class InputField extends Component {
  @tracked isFocused = false;

  handleFocus = () => {
    this.isFocused = true;
  };

  handleBlur = () => {
    this.isFocused = false;
  };

  handleInput = (event) => {
    this.args.onInput?.(event.target.value);
  };

  <template>
    <input
      type="text"
      class={{if this.isFocused "focused"}}
      {{on "focus" this.handleFocus}}
      {{on "blur" this.handleBlur}}
      {{on "input" this.handleInput}}
      value={{@value}}
    />
  </template>
}

Using fn helper for arguments:

// app/components/item-list.gjs
import { fn } from '@ember/helper';
import { on } from '@ember/modifier';

<template>
  <ul>
    {{#each @items as |item|}}
      <li>
        {{item.name}}
        <button {{on "click" (fn @onDelete item.id)}}>
          Delete
        </button>
      </li>
    {{/each}}
  </ul>
</template>

The {{on}} modifier properly cleans up event listeners, supports event options (passive, capture, once), and makes event handling more explicit.

Reference: Ember Modifiers - on