Communication Between Distant Components

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:

  1. Upward to another Ember object (route, component, etc)
    1. Involving the URL: making the route invoke model() (like discussed here and here)
    2. Not involving the URL (like this but without implicating the URL or model(), or, to a parent component)
  2. Downward to child components (like this)
  3. Sideways or to Ember objects not in the same hierarchy (like we will see in this article)
The blue arrows represent data and the purple arrows represent actions. These are shown in a few but apply to **each and every** component. If the deepest nested component needs to send a message to the route, it will have to send an action up to its parent, the parent to its own parent, and so on following the ancestry until it hits the route.
The blue arrows represent data and the purple arrows represent actions. These are shown in a few but apply to each and every component. If the deepest nested component needs to send a message to the route, it will have to send an action up to its parent, the parent to its own parent, and so on following the ancestry until it hits the route.

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?

Data Down Actions Up

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.

A case for The Bus

Since our activity-feed component…

  • can potentially be used in any component, and
  • will not interfere with URLs or models of current routes

…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.

A word of caution!

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.

Enjoyed this article? Join Snacks!

Snacks is the best of Ember Octane in a highly digestible monthly newsletter. (No spam. EVER.)