The {{fn}} helper (3.11)

Learn about Ember's conceptual shift in binding actions at the source, rather than at the invocation site.

Summary

Traditionally in Ember, actions have referred to either the built-in action modifier, or the built-in action helper:

{{! action modifier }}
<button {{action 'increment'}}>Add 1</button>

{{! action helper }}
<button onclick={{action 'increment'}}>Add 1</button>

Both forms are responsible for quite a few things:

  1. Look up the increment() function on the actions hash of the component
  2. In the case of the modifier, add a "click" event listener
  3. Accept & curry arguments
  4. Bind the correct this context

For all these reasons, Actions have historically been a little confusing to work with at times, and they can feel like one of the more "magical" parts of the framework.

This is why the next few features, starting with the {{fn}} helper, are all about decomposing Actions into new primitives that each have their own responsibility.

Before we introduce these new primitives, though, we need to learn about a new method we have for creating Actions: the new action function import:

import Component from "@ember/component";
import { action } from "@ember/object";

export default Component.extend({
  count: 0,

  increment: action(function () {
    this.incrementProperty("count");
  })
})

What does that action import do? It binds the this context of our component's increment method. Note that this is one of the things the {{action}} helper/modifier did before, but now instead of doing it in the template every time we reference a method, we can do it once from the component file. This is nice because it means that no matter who references our increment method from a template, it will be "safe" to call – the this context will always be bound to the component instance.

So, this is a bit of a conceptual shift from how we've created Actions before. Usually we create them in the template, but now we're creating them in our JavaScript component file. By doing it in the JS at the definition site of the function, we only have to worry about binding once. The template can now invoke the action n times, just by calling the function, and without worrying about binding or this context.

Why is the JavaScript import called action, if all it's doing is binding the context? You could imagine it being called bind or autobind. It's a bit confusing since the word "action" has become so loaded at this point in Ember, but the conceptual model here is: the action import turns a method into an Action, and Actions are safe for use by templates.

So, if you need to invoke a method from a template, turn that method into an Action using the action import.

Going forward, this will be the only notion of "actions" in your Ember code! That's because the other responsibilities of the classic action helper/modifier are being split up, starting with the ability to accept and curry arguments. And that's what this new {{fn}} feature is all about.

Once we create an action in our component file using the new import, we can reference it in our template like this:

<button onclick={{this.increment}} />

and everything will work as we expect. But what if we want to pass in arguments?

<button onclick={{this.incrementBy 5}} />

This won't work, because Ember will expect incrementBy here to be a helper that takes in an arglist. Instead, we can use the new {{fn}} helper to pass an argument into our incrementBy function, like this:

<button onclick={{fn this.incrementBy 5}} />

The fn helper applies the arglist to the function we give it, so that when the event handler callback occurs the function will be invoked with the args that we passed in.

One way to think of it that might be helpful is fn is like invoking an anonymous function:

 {{! You can think of it like this --}}
<button onclick={{ () => this.incrementBy(5) }} />
<button onclick={{fn this.incrementBy 5}} />

So, that's how fn lets us pass in arguments directly to actions from our template.

More than that, though, fn is actually currying these arguments, meaning it can be used multiple times allowing for better composition throughout your component hierarchy. If you've used curried actions in your Ember template code in the past, you can use {{fn}} in exactly the same way.

So, we can see that the {{fn}} helper takes on the responsibility of arg passing and currying that used to be handled by {{action}}. Next we'll learn how {{on}} lets us easily add event listeners.

Questions?

us, or ask in #media on Discord