Ember Actions: Best Practices

In Ember, actions have always been kind of tricky to deal with. The recent introduction of closure actions, although being more powerful, made it harder for the layman to understand. As a result I was still using the old sendAction() way of triggering actions. Then I read this very good article by Sam Selikoff which clarified lots of things. If you haven’t read the article yet, I highly recommend you to do so now.

Although Sam presents many ways you can handle actions, he doesn’t really tell us what’s the best way. Ember’s conventions over configuration paradigm makes creating apps a breeze because we don’t have to reinvent the wheel and instead apply proven patterns to solve common problems. Actions are one of those things that everyone uses, but for some reason I couldn’t find any strong conventions to help me along the way. Every-time I created a new component which sent actions, I always come back to the same question:

How do I name this action?

And while you might think it’s not worth spending much time thinking about it, good naming will impact how you structure your code, help your team communicate, and improve maintainability.

There are only two hard things in Computer Science: cache invalidation and naming things — Phil Karlton

And it doesn’t look like I am the only one in that situation. After a quick look at the add-on landscape, I realized that everyone had a different way to handle actions. As a result, I came up with a set of conventions (or at least, best practices) that I was able to successfully implement on relatively large codebases.


Triggers and Handlers

Let’s start with a few definitions. An action is a signal which is created by a trigger and is caught by a handler. The trigger’s role is to emit the action, but it doesn’t make any assumptions on how the action is processed (for instance, a button). On the other hand, the handler is processing the action, but doesn’t care how the action is triggered. It’s basically an application of the principle of least knowledge.

In the first example,onSelectUser makes an assumption regarding the handler: the handler is going to “select” the user. However, in the second example,onClick just tells what the component does and nothing more. This allows you to reuse the component in various contexts:

Trigger names start with “on” + event name, and they are camelCase (unlike HTML5 “onclick”). Eventually, you can use a different prefix as soon as it is a temporal signifier like “before” and “after”. Example of triggers: onClick, onSelect, beforeSubmit, etc.

Handler names — because they perform the action — start with a verb: saveUser, delete, toggleButton. They are also camelCase.


Triggers

Let’s imagine a simple submit-button component that emits an onSubmit action when clicked. Here the component is just bubbling the action to the parent component:

The first code example is the old way of bubbling actions. You have to call sendAction() to pass the action to the parent component. But this approach is very hard to debug and maintain. See how the click() function in the actions hash is not doing anything else than wiring things up? Many new Ember users get stuck because 3 levels deep, a component is not forwarding the action to the parent component using sendAction().

In the second example, the action is defined like any regular attribute. The advantage is that it makes it very easy to document the component’s interface. You get a sense of what the component expects just by reading the first 10 lines. Here is an example:

Here we take full advantage of the ES6 short syntax myFunction(){} to keep things clean and organized.

There is another advantage to this approach, it doesn’t return an error if the action is not defined by the handler. It will just execute the empty function. It’s useful when calling an action programmatically:

You may also initialize it with a default function call:

A word of caution: this mechanism is only used to bubble actions up to the parent component, if the component itself is handling the action, it is preferable to define it in the actions hash to avoid any ambiguities (see next section).

Handlers

Action handlers are responding to actions from child components. In that case, prefer passing actions by name and define handlers in the actions hash.

Sometimes, you want to bubble an action from a child component. Imagine a form using a submit button component and bubbling the submit action to the controller. Although it’s not necessary, it’s always a good habit to wrap it in an action helper. It helps further differentiate regular attributes from actions in the template:

But the biggest advantage of using the action helper is to pass additional attributes:


TL;DR

To summarize, clearly separate triggering and handling actions:

  • Actions start with “on” (e.g. onSubmit) and are defined at the top of the file like attributes.
  • Handlers start with a verb (e.g. saveUser) and are defined in the actions hash.
  • Always use the actions helper to trigger or pass actions.

Here is what an application composed of 2 components user-form and submit-button might look like:

Thanks for reading 🙂 Here is more information about actions and components: