Last reviewed in October 2015 with Ember 2.0 Update to Ember Octane in progress!
Components are isolated entities that interact with the surrounding context through their interface.
The data down, actions up pattern is a best practice for component communication.
Components send actions (messages) up through their sendAction
API and rerender when any data bound to them changes. These bindings are usually “one way” (immutable) so that data can't be modified in-place but receive a copy from a parent instead.
{{my-component item=model action='navigateTo'}}
Communication can happen:
Let's assume that the diagram above represents part of our real-world application. An application consisting of 30 routes and more than 50 components.
The little green component is a new widget in charge of logging activity throughout the site. It may be used within any component, multiple times if required.
As hinted by the dashed arrow in the diagram, how exactly would components communicate sideways?
As we said before, components communicate through their interface. Our widget's signature could therefore read:
{{activity-widget feed=feed}}
Meaning that every component that includes it has to supply a feed. And their ancestors too, to “forward” it on to where it's needed. Sending feed actions up and feed data down to every possible component.
It would require all the hierarchy to be aware of these actions & data and handle them like hot potatoes.
All of a sudden the beautiful sexy architecture becomes a crippled old lady.
Since our activity-feed
component…
…we have a good candidate for an event bus.
Super easy with Ember! We'll start by creating a FeedService
that extends Ember.Evented
, a mixin that enables pub/sub with methods like on
, off
, trigger
and so on.
// app/services/feed.js
export default Ember.Service.extend(Ember.Evented);
// app/components/activity-feed.js
export default Ember.Component.extend({
feedService: Ember.inject.service('feed'),
activities: [], // the template will display this array
listen: function() {
this.get('feedService').on('activity', this, 'logActivity');
}.on('init'),
logActivity(activity) {
this.get('activities').push(activity);
},
// remember to remove what you bind upon component destruction
cleanup: function() {
this.get('feedService').off('activity', this, 'logActivity');
}.on('willDestroyElement')
});
// app/components/sample-component.js
export default Ember.Component.extend({
feed: Ember.inject.service(),
actions: {
click(e) {
// log
this.get('feed').trigger('activity', { name: 'click', event: e });
// handle click
// ...
}
}
});
This approach can be extremely useful and should nicely complement the linked articles above.
Here are two cases that might justify the use of an event bus.
The examples above are not uncommon in real-world scenarios.
It's definitely more likely, however, that communication between your components can be solved with “data down actions up”. That is, passing in stuff through components’ interfaces.
The event bus is a great fit for loosely coupled components yet makes the architecture difficult to reason about, impacting testing as well.
In the beginning of this section “A case for The Bus” we started off with a checklist. Before rushing to the pub/sub solution, it's important to make sure that there's no better means of passing messages around.
On the other hand, “Data down actions up” ain't no religion. It's important to know our tools.
Snacks is the best of Ember Octane in a highly digestible monthly newsletter. (No spam. EVER.)