Part 2: Ember closure actions

3 min read • 18th June 2018
Code snippet of parent

Note
Ember versions > 1.13.x

In old versions of Ember (< 1.13) sendAction was the only way for a component to call an action on a parent scope. In 1.13, with the so called closure actions, a more intuitive and flexible way of calling actions was introduced, yielding the old way redundant.

So what does this offer? Functions down actions up! Yes, in Ember 1.13 and above, a function can be simply passed down to components nested down in deeper hierarchy without writing multiple sendAction calls like it was required in < 1.13

"Enough talking, give us an example" — wasn't that the statement running in your head? 😂 Read below:


Link to this sectionThe traditional approach using sendAction

Parent layout:

// controllers/foo/show.js
export default Controller.extend({
  actions: {
    sendData(data) {
      console.log(data);
    }
  }
});
<!-- templates/foo/show.hbs -->
{{foo-bar-parent sendData="sendData"}}

First child component:

// components/foo-bar-parent.js
export default Component.extend({
  actions: {
    sendDataToController() {
      this.sendAction('sendData', 'foo');
    }
  }
});
<!-- templates/foo-bar-parent.hbs -->
{{foo-bar-child sendDataToController="sendDataToController"}}

First child's child component:

// components/foo-bar-child.js

export default Component.extend({
  actions: {
    sendDataToParent() {
      this.sendAction('sendDataToController', 'foo');
    }
  }
});
<!-- templates/foo-bar-child.hbs -->
<button {{action "sendDataToParent"}}>Click me</button>

The amount of action binding! Isn't it confusing? Now, let's transform the same example to closure action:

Parent layout:

// controllers/foo/show.js
export default Controller.extend({
  actions: {
    sendData(data) {
      console.log(data);
    }
  }
});
<!-- templates/foo/show.hbs -->
{{foo-bar-parent sendData=(action "sendData")}}

First child component:

// components/foo-bar-parent.js
export default Component.extend({
  // NO-OP
});
<!-- templates/foo-bar-parent.hbs -->
{{foo-bar-child sendData=(action @sendData)}}

First child's child component:

// components/foo-bar-child.js

export default Component.extend({
  // No-OP
});
<!-- templates/foo-bar-child.hbs -->
<button {{action @sendData}}>Click me</button>

However, this is a very simple use case, but in real world scenarios we might have to perform some actions in intermediate components. Also, what if the controller did not specify the sendData action and that the action is optional? It would throw an error. This could be prevented by adding a guard on the child component.

Note
Did you also notice the @ sign used in the templates? It means, that the function has been passed from the parent and is not part of that component.


Note
This does not mean sendAction is totally removed, but is available until last versions of Ember 3.X and would result in deprecation warnings to help apps/add-on authors to migrate easily. A more detailed explanation into Ember's closure actions is in here.