Composable Helpers and Route Actions: Two Ember Add-Ons You Should Know

Katherin Siracusa
AlphaSights Engineering
7 min readApr 13, 2016

Two new Ember add-ons were recently released and have strummed up a bit of interest in the ember community: the route-action-helper and composable-helpers add-ons. I tried them out in our app at AlphaSights and I was pleasantly surprised to find a use case for both that helped simplify a commonly recurring situation in our code. Before jumping into that, here are some quick examples and a brief explanation of each:

route-action-helper

The route-action-helper simplifies the process of bubbling closure actions directly to the route. In Balint Erdi’s Rock’n’Roll with Ember.js book, he suggests that (page 110):

“A good rule of thumb is that any action which only modifies the state of the controller itself be handled on the controller. Actions that have a broader-reaching effect should be handled by routes.”

The route is, after all, where we most often query the store and obtain data. Though the model lives in the controller, it originally comes from the route. Until now, I’ve been putting most of these create and update kinds of actions in the controller, but this often leads to duplicated actions and rather bloated controllers.

The guides state that with the upcoming implementation of routeable components, controllers’

…responsibility is strictly limited to two avenues:

Controllers maintain state based on the current route. In general, models will have properties that are saved to the server, while controllers will have properties that your app does not need to save to the server.

User actions pass through the controller layer when moving from a component to a route.

Rather than having the actions simply pass through the controller, adding “glue code,” we can bypass this step with route-action-helper. The helper, simply enough, takes the action, finds the handling route and bundles them into a closure action. As mentioned in this blog post about the add-on, once routable components land, the transition to a world without controllers will be easy.

I found the route-action-helper to be particularly useful when working with nested routes. Let’s say you have a teams route, with nested active and inactive users routes. Most of the changes we’ll want to make to users are the same, regardless of their active state. Rather than repeating an updateUser action that saves to the store in both the active-users and inactive-users controllers, we define the function just once, in the teams route, and use the route-action-helper in each template to bubble the action to the route.

This keeps the duplication out of the controllers and provides one common place to go to handle the server’s response and one source of truth for the data.

composable-helpers

Composable-helpers is another newer add-on which provides a useful library of helpers that can be used together. The readme says this also allows for more declarative templating; we thus have a more readable template and a component less cluttered with boiler plate. Some of the string helpers can immediately be used to replace rather simple computed properties: using camelize and capitalize helpers, for example, makes simple string changes easy. Array-related helpers, such as filter-by, also remove noise from the component but their purpose in the template remains clear (for example: filter-by “active” true users).

Perhaps the most powerful of these helpers are the action helpers. One of which, the pipe helper, “pipes the return values of actions in a sequence of actions.” This is particularly useful when performing subsequent actions. The helper is promise-aware, so if one of the actions returns a promise, the next action will either receive the value of that promise or realize the promise has been rejected and the chain of actions will come to a halt.

all together now

Let’s say our app now needs to be able to sort team-members into sub-groups, with members of the same type (active with active, inactive with inactive). We should be able to create these sub-groups and then drop team-members into them.

I originally implemented a pattern of clicking a button to display the edit form, entering a group name and pressing save. On save, the action defined on the new-group component would bubble up to the specific type of user’s controller. At this level, we have access to the team_id (necessary for the creation of a group), and could create the record and save it to the store and database. We would then toggle the “isAddingNewGroup” property to hide the form only once the new group’s existence was confirmed.

There were a few issues with this approach, however. For one, the createNewGroup action had to be repeated for each kind of user, as each has their own controller. Having recently been thinking about trying to move these kinds of actions into the route, anyway, it might make sense to move this action to a common parent route, the team route. For that, I knew I could use the route-action. But then how to handle the edit- or display- “state” of the controller to show the form? Using route-actions and composable-helpers, together, I found an elegant solution that enabled me to keep these actions where they belong.

Start with a form in the template:

And a component for the form:

A group is associated with a team and this information needs to be present when creating the group, but the team doesn’t really need to be passed down into the new-group component. We’re creating a group of users in a team, here, but this component could potentially be used for any number of group creations elsewhere and including the team would make this impossible. So, in the next level up (the higher level template that houses the new form and already has the team context), we have:

And in the route:

I like this approach for a few reasons:

1) We need the context of the “team” from this level, in addition to the name passed in on the lower component. Using the route-action-helper, we can still use closure actions to collect arguments along the way (the team) and then send the action directly to the route to create the record in the store. The route-action-helper allows this all to work seamlessly.

2) The isAddingNewGroup state should exist on the controller (it is related to a short-term state) but should only be toggled if the new group is actually created. If the promise from createNewGroup is resolved, we can then toggle isAddingNewGroup, which will hide the form. If the promise is not resolved, the sequence of events will be aborted, the property will not be toggled and the show form will remain open, to show errors or in some other way, let the user know their intended action was unsuccessful. The pipe composable helper makes this process easy and clean.

This pattern, of performing some significant, data-changing action and subsequently performing a more ancillary, short-term state-like action, keeps arising in our application. Using the route-action-helper and composable-helpers, we can take care of this in a fairly straightforward way, without much duplication and without having to worry about unintended side effects.

I drafted much of this post in the days leading up to EmberConf 2016. At the conference, Lauren Tan, of Dockyard gave a talk entitled Idiomatic Ember which discussed both of these add-ons and went through a few other Ember “best practices”. While I was pretty happy to hear that these “best practices” were in line with this post and my recent understanding of certain parts of Ember, there was some chatter on slack about whether these are indeed “best practices,” and whether actions like these should live in the route. It sparked a bit of a discussion within my own team, as well, which made me question whether this is indeed the “best” practice. One particularly interesting argument for keeping these actions in the controller is that the store is automatically injected in the controller, and much of the other manipulations we do on data reside there. Why not do more permanent manipulations here as well? What use would the store have, here, if not for things like this?

But just because the store is available here does not necessarily mean it needs to, or even should, be used. By separating these kinds of actions, the intent of both types becomes more clear. Actions in the route will most likely be changing data in some way, while those on the controller are preparing data for the template.

I would highly recommend watching Lauren’s talk once it is released (slides are here), and would love to hear what you think about these add-ons. Have you found use for them in your app? Would you use the route-action-helper to move certain actions to the route? Or should these stay in the controller?

--

--