Brief history of passing actions in components
The ability to pass actions to components is not a new feature for component use. The previous action system used bubbling to pass user behavior to a parent scope. For example, a user clicks a button within a component, this then triggers an action which bubbles through several controllers to finally be handled on a route. This way of bubbling actions was challenging to debug and required a good amount of boilerplate code. Looking forward, Ember was geared to become more component-driven and delivered a cleaner and more flexible way to pass functions with the Ember 1.13.0 Closure Actions.
First let’s take a look at how we used to bubble actions
In our user route we have a property
count, and an action
addAction to increment that property.
Here we call our component and bind
add to the
addOne function and bind our count property object to count that property.
Our Done button will now trigger the
add action within our done-button/component.js.
add will send our bound action
addOne and bubble until finding the action location and trigger within the user/route.js. Because we have to bubble these actions up through each layer of the application (until we reached the top), nested layers of your application required a lot of boilerplate code. Every component requires an action to be defined so that it could simply be sent to another action. Now let’s compare this to using closure actions.
With closure actions, we will maintain our
addOne action in our route and pass that action into the component such that the current scope of the action gets passed down to the component as well. Then, we can trigger the invocation of this action directly from the component. We can do this by accessing the
attrs property of the component. The
attrs contain all of the attributes passed into a component, including closure actions.
Our user/route.js and our done-button/template.hbs will remain the same, however, we will need to modify our user/template.hbs and done-button/component.js.
Our new action helper takes the name of our action as a parameter, in our case
addOne. The helper returns the addOne function that we defined in the user/route.js, wrapped in the current scope. Basically we are giving our done-button component an attribute called
add and setting it equal to the addOne function and an object attribute of count set equal to the count property we defined in the user route. The done-button component now has access to the addOne action in our user/route.js and the scope that the addOne action has access to in the parent template.
We can now access these attributes within our component by calling
this.attrs. Let’s take a closer look.
thisis the done-button component itself.
.attrsare the attributes of count and add that we assigned in our done-button component call in our parent template user/template.js.
If we were to debug into our
this.attrs, here is what we would see: our count object attribute and our addOne function attribute.
Let’s update our add function to take advantage of our new attrs. Now when the Done button is clicked and we enter the add action, we will execute the passed-in action function
addOne directly from the child component.
Closure actions allow us to define actions and use them across components. They help us avoid boilerplate code and expose isolated components to the context of parent templates, allowing actions to be passed down through a series of nested components and triggered through
NOTE: All code examples use the
--pod structure. Actions are handled within routes.