Three Reasons Ember.js Closure Actions Rule

Closure actions were introduced in Ember 1.13. They are a huge improvement over the previous action infrastructure. In this post, I’ll highlight some of the things that make closure actions so awesome.

1. Improved Syntax

The old style of action handling was not elegant. Here’s how passing action handlers used to look:


<!-- /templates/components/parent-component.hbs -->
{{old-component someAction='handleAction'}}


// /components/old-component.js
click() {
  this.sendAction('someAction');
}

The parent template passed old-component the someAction property. Then, when old-component sent the action named someAction (via a string), parent-component’s action handler was invoked. The fact that someAction was hooked up to an action in the parent was completely implicit.

The new syntax makes action-passing much more elegant. Here is how we do the same thing now:


<!-- templates/components/parent-component.hbs -->
{{new-component someAction=(action 'handleAction')}}


// components/new-component.js
click() {
  this.attrs.someAction();
}

It’s obvious that when someAction is passed into new-component, it’s an action. When we want to trigger the action, we can just call the function. Another added benefit is that a JavaScript error will be thrown on page load if the component does not define the action hooked up to the template. Before, the error wouldn’t surface until the action was actually attempted.

2. Easier Data Propagation

One of Ember’s core concepts is “data down, actions up” (DDAU). Put simply, data should flow down through an application, and actions (e.g., clicking, typing, etc.) should be responsible for bubbling information back up.

Imagine a scenario where we are displaying a list of superheroes for different comic book publishers. We have a couple of related components. At the very bottom, we have a simple component responsible for displaying and selecting a superhero. When we select a superhero, we need to do some stuff in the application controller, which is at the very top of our application.

Before closure actions existed, we had to bubble that action up through each layer of the application until we reached the top. Every component defined an action that simply sent another action. In other words, it was a lot of boilerplate code.

Closure actions help us avoid most of that boilerplate code. We avoid it by accessing the special attrs property of the component. This object contains all of the attributes passed into a component, including closure actions.

Let’s map out our scenario from the top down.


<!-- /templates/application.hbs -->
{{comic-publishers publishers=publishers selectSuperhero=(action 'selectSuperhero')}}


<!-- /templates/components/comic-publishers.hbs -->
{{#each publishers as |pub|}}
  <h2>{{pub.name}}</h2>
  {{superhero-list superheroes=pub.superheroes selectSuperhero=attrs.selectSuperhero}}
{{/each}}


<!-- /templates/components/superhero-list.hbs -->
{{#each superheroes as |hero|}}
  {{superhero-list-item hero=hero select=attrs.selectSuperhero}}
{{/each}}


// /components/superhero-list-item.js
click() {
  this.attrs.select(this.get('hero'));
}

Because we are leveraging the attrs property, we don’t have to define the boilerplate action handler in superhero-list.js or in comic-publishers.js, and our selection still bubbles all the way up to the application controller.

3. Added Action Currying

Another awesome feature of closure actions is that they support currying. At any level in the chain of components, I can tack additional parameters onto an action.

Revisiting our scenario from above, let’s say the application controller also needs to know which publisher the selected superhero belongs to. That’s easy; we can curry the action in the comic-publishers.hbs template to append the publisher and send that action up to our controller. Let’s check it out!


<!-- /templates/components/comic-publishers.hbs -->
{{#each publishers as |pub|}}
  <h2>{{pub.name}}</h2>
  {{superhero-list 
      superheroes=pub.superheroes
      selectSuperhero=(action 'selectSuperhero' pub)}}
{{/each}}


// /components/comic-publishers.js
actions: {
  selectSuperhero(publisher, hero) {
    this.attrs.selectSuperhero(publisher, hero);
  }
}

Now, whenever the selection bubbles up, the publisher is added to the parameters.

See the Code

All of the code from this post can be found here. I would love to know what other wins you’ve seen from using Ember’s closure actions.

 
Conversation
  • Bob says:

    What did you read to understand ember closures? I’m trying to understand how your last 6 code snippets work not what their result is.
    For instance why “publishers=publishers”?

  • Christopher Bathgate says:

    Its all sun shine and rainbows until you try it with a route.

  • Comments are closed.