Understanding why component actions don't bubble up


#1

I know that currently component’s actions don’t bubble up. I’m sure the core team made a right decision.

However, it would be interesting to know what has lead to that decision.

Consider the following composition of components.

{{#component-one}}
    {{#component-two}}
        {{#component-three}}
            <button {{action "update" "new-task-title"}}>Update</button>
        {{/component-three}}
    {{/component-two}}
{{/component-one}}

What’s the disadvantage of the submit action/event bubbling up all the way to the Application Route in the following order:

component-three > component-two > component-one > route-view > route-controller > route > application-route

And then have an ability to intercept the action at any level required. For example, let’s say component-one needs to perform something when “update” is triggered and handle it with “saveTask” action.

{{#component-one update="saveTask"}}
    {{#component-two}}
        {{#component-three}}
            <button {{action "update" "new-task-title"}}>Update</button>
        {{/component-three}}
    {{/component-two}}
{{/component-one}}

This discussion is more about curiosity rather than a proposal. I simply would like to know the disadvantage of the above as I’m not aware of all the complications it may cause.


#2

Actions do bubble up, they just don’t go through the entire view hierarchy. They go:

component-three -> template's controller -> template's route -> parent route -> application route

This may well change in the age of routeable components. My guess as why the existing behavior is the way it is:

Views were intended to be purely for display logic (such as whether or not an accordion is expanded), while controllers had actual application state stuff. When you sent an action from the template based on some user interaction, 90% of the time that was interacting with the application state, so, straight to the controller (unless you explicitly overrode it to go the view, eg. the accordion expander from above). If you were interacting with something else in the application, you’re guaranteed to find a common ancestor somewhere in the route hierarchy that can start passing stuff down to.

In our new component-rich world, a lot of these boundaries are blurred. The old arguments don’t make quite as much sense, so I’ll be curious to see if this changes.


#3

I get the following error

Uncaught Error: <my-app@component:component-three::ember1423> had no action handler for: update

#4

Oops, left a very (the most?) important part out! You need to explicitly link your component’s action to the controller’s action with sendAction(). This is because components are designed to be reusable across applications, including ones that you don’t own (ala addons). Having to explicitly link the actions together prevents the component from triggering unintended stuff in your application.

It will look something like this:

// Component Template
<button {{action "someAction"}}>Click me!</button>

// Component JS
actions: {
    someAction: function(){
        this.sendAction("someAction"); // Exposes the action 
    }
}

// Application template
{{my-component someAction="someAction"}} {{!-- Links the exposed action to the controller --}}

// Application controller
actions: {
    someAction: function(){
        alert("I got clicked!");
        return true; // Can be bubbled like normal
    }
}

You can change the names and stuff around if what’s in the template would cause a collision in your application. You might want to check out the guide section for this.