@action, {{on}} and {{fn}}
Octane provides a set of new conventional APIs for creating and adding event handlers and actions to your components and templates:
- The
@action
decorator - The
{{on}}
modifier - The
{{fn}}
helper
These are meant to replace the {{action}}
helper/modifier, which will be deprecated in the future. You can use them like this:
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class TodoComponent extends Component {
@action
toggleCompleted(isComplete) {
// ...
}
}
<button type="button" {{on "click" (fn this.toggleCompleted true)}}>Complete</button>
Benefits of @action
, {{on}}
, and {{fn}}
{{action}}
has a number of functions, including:
Creating action callbacks, which bind the context of the callback (the component/controller).
-
Adding arguments to action callbacks:
<!-- passes 123 to the 'setValue' action --> <MyComponent @onClick={{action 'setValue' 123}} />
-
Adding event handlers to elements (when used as a modifier):
<button type="button" {{action 'sayHello'}}>Say Hello!</button>
The new APIs split each of these pieces of functionality out into one clearly defined API:
-
@action
is a decorator that binds a method to the context its used in -
{{on}}
is a modifier that's used to add event listeners to DOM elements -
{{fn}}
is a helper that adds arguments to another function or callback
This keeps the responsibilities clearly delineated, and makes it much easier to reason about what each individual API is doing.
Getting used to @action
, {{on}}
, and {{fn}}
The @action
Decorator
In Ember Octane, actions are no longer defined on the actions
object of a component or controller. Instead, they are standard class methods decorated with the @action
decorator.
Before:
import Component from '@ember/component';
export default Component.extend({
actions: {
doSomething() {
// ...
}
}
})
After:
import Component from '@glimmer/component';
export default class ExampleComponent extends Component {
@action
doSomething() {
// ...
}
}
The decorator leaves the method intact without any changes, so you can continue to use it like a normal method. This also means that you can reference the action directly in templates, instead of using strings.
Before:
<button type="button" {{action "doSomething"}}>Click Me!</button>
After:
<button type="button" {{on "click" this.doSomething}}>Click Me!</button>
The decorator is important, as it binds the action directly to the class so it can reference it later on.
The {{on}}
Modifier
The API for {{on}}
is the same as JavaScript's native addEventListener
. It receives the event name as the first argument, and a callback function as the second argument:
<button type="button" {{on "click" this.handleClick}}>Click Me!</button>
The event can be any event name, not just the click
event, which makes {{on}}
perfect for handling any kind of DOM event. For a list of native browser events, see the MDN documentation. The callback function will receive the event as its first argument:
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class ExampleComponent extends Component {
@action
handleClick(event) {
event.preventDefault();
}
}
This is a replacement for {{action}}
when it is used as a modifier:
<!-- Before -->
<button type="button" {{action 'handleClick'}}>Click Me!</button>
<button type="button" {{action 'handleDoubleClick' on="doubleClick"}}>Double Click Me!</button>
<!-- After -->
<button type="button" {{on "click" this.handleClick}}>Click Me!</button>
<button type="button" {{on "dblclick" this.handleDoubleClick}}>Double Click Me!</button>
You can also pass additional options such as passive
and once
as named parameters to the modifier:
<button type="button" {{on "click" this.handleClick passive=true}}>Click Me!</button>
If you ever used the value
parameter of {{action}}
, there is no direct equivalent for {{on}}
. You should instead write an action that gets the value for you.
Before:
<input value={{this.value}} onchange={{action (mut this.value) value="target.value"}} />
After:
<input value={{this.value}} {{on "change" this.updateValue}} />
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
export default class ExampleComponent extends Component {
@tracked value;
@action
updateValue(event) {
this.value = event.target.value;
}
}
If you want to pass additional parameters to the callback function, you must use the {{fn}}
helper. {{on}}
does not receive any additional parameters.
The {{fn}}
Helper
{{fn}}
is a helper that receives a function and some arguments, and returns a new function that combines. This allows you to pass parameters along to functions in your templates:
<button type="button" {{on "click" (fn this.handleClick 123)}}>Click Me!</button>
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class ExampleComponent extends Component {
@action
handleClick(value) {
console.log(value); // 123
}
}
This is a replacement for passing parameters to the {{action}}
modifier or helper:
<!-- Before -->
<button type="button" {{action 'handleClick' 123}}>Click Me!</button>
<MyComponent @onClick={{action 'handleClick' 123}} />
<!-- After -->
<button type="button" {{on "click" (fn this.handleClick 123)}}>Click Me!</button>
<MyComponent @onClick={{fn this.handleClick 123}} />
© 2020 Yehuda Katz, Tom Dale and Ember.js contributors
Licensed under the MIT License.
https://guides.emberjs.com/v3.25.0/upgrading/current-edition/action-on-and-fn