Spotlight: Ember Closure Actions

I am really excited be spotlighting one of my favorite Ember features in a long time, closure actions. The goal of this blog post is to get you familiar, at a high level, with some of the basic closure action behaviors and help establish a solid foundational understanding.

Closure actions are brand new as of the current Ember v1.13.0 release. This feature creates a whole new action handling approach which simplifies the entire process in general, as well as allows for the new function-passing solution to replace the old action bubbling mechanism. Under the hood, the {{action}} helper is now improved to allow for the creation of closed-over functions which pass the action handlers between components and controllers.

Here is a quick example highlighting the change:

{{! NOTE: old action handling approach }}

<button {{action "announce" on="click"}}>Winter is Coming</button>

This would be rewritten using a closure action, utilizing the native onclick DOM event in attribute context as follows:

{{! NOTE: now we have a closure action }}

<button onclick={{action "announce"}}>Winter is Coming</button>

Used this way, the resulting template render logic would be constructed as follows in plain javascript:

var btn = document.createElement('button');
var actionFunction = (function(context) {
  return function() {
    return context.actions.announce.apply(context, arguments);
  };
})(context);
btn.onclick = actionFunction;

Under the hood, you can see there is no crazy magic happening. Ember is simply attaching a function to a click handler for a DOM node using btn.onclick = actionFunction;. That function just calls the name of the action plucked off the actions hash within the current context.

Closure actions may also be invoked via Handlebars value context:

{{! NOTE: two invocations using Handlebars value context }}

{{got-input focus-out=(action "sendRaven")}}
{{yield (action "gatherBannermen")}}

Consider the old approach of passing down the action name as a “string” from a higher level scope to a lower level scope and then sending an action back up the chain to call the original action handler function:

{{! NOTE: old approach }}
{{! app/templates/index.hbs }}

{{jon-snow action="swingSword"}}
// app/components/jon-snow.js
export default Ember.Component.extend({
  click() {
    this.sendAction();
  }
});

Note: this old system of action bubbling quickly falls apart when attempting to pass an action through a nested hierarchy of components.

With closure actions, the (action) helper simply wraps the action in the current scope and returns back that function — as discussed previously. Now you will execute the passed-in action function directly from the child component.

// app/controllers/index.js
export default Ember.Controller.extend({
  actions: {
    swingSword() {
      // Action
    }
  }
});
{{! app/templates/index.hbs }}

{{jon-snow swing=(action "swingSword")}}
{{! app/templates/components/jon-snow.hbs }}
{{! NOTE: The `(action)` keyword is optional here. }}
{{! I'm using it to be more explicit as a visual marker when scanning templates. }}
{{! It also allows me to pass additional parameters if needed. }}

{{longclaw-sword attack=(action swing)}}
// app/components/longclaw-sword.js
export default Ember.Component.extend({
  click() {
    this.attack();
  }
});

Much more flexible and much cleaner! Here you see I am easily passing the closure action through a hierarchy of nested components.

Additionally, the closure actions approach allows you to {{yield}} an action back up to a block. For example:

{{! app/templates/index.hbs }}

{{#tyrion-lannister drink=(action "haveGlass") as |drinkWine snark|}}
  {{! calls the tyrion-lannister component `drinkWine` yielded attr }}
  {{!   --> calls the "haveGlass" action on the outer scope }}
  <button onclick={{action drinkWine}}>Drink Wine</button>

  {{! calls the yielded "snark" action from the tyrion-lannister component scope }}
  {{#my-button doSnark=snark}}Make Snarky Comment{{/my-button}}

  {{! calls the "cavort" action on the outer scope }}
  <button onclick={{action "cavort"}}>Cavort</button>
{{/tyrion-lannister}}
{{! app/templates/components/tyrion-lannister.hbs }}

{{yield drink (action "snark")}}
// app/components/tyrion-lannister.js
export default Ember.Component.extend({
  actions: {
    snark() {
      // Action
    }
  }
});

These are just some of the cool features afforded by closure actions. Also, keep in mind this is not removing the existing usage of “string” based action handling. If you wish, you can continue to write actions using the old approach. Please recognize that I have not touched on slightly move advanced features such as currying arguments in this post — just know that the ability to wield powerful behaviors are unlocked with this new action handling mechanism.

To get a taste of some of these awesome closure action capabilities working together in tandem, check out the following example. It combines several great features — block parameters, closure actions (specifically yielding up an action to a block within a higher level scope), and the {{component}} helper.