Paul Cowan

Nomadic cattle rustler and inventor of the electric lasso

Emberjs - Communication Between Components

Communication Between Components

A question that I see frequently asked is how can a parent component communicate with child components.

A child component can communicate with a parent in a number of ways with the most commmon at this time of writing with ember 1.11.1 is to use sendAction.

For example, a parent component can tell a child component what its primary action is or give a named action to its children:

And the child component can then call this action:

child1.js
1
2
3
4
5
6
App.XPersonComponent = Ember.Component.extend({
  actions: {
    callParent: function(person) {
      this.sendAction("action", person);
    }
  },

I have never warmed to the abstraction of callable actions existing in an actions hash and from what I can tell, it will be possible in ember 2.0 to simply pass actions around as plain old javascript functions which is something that I welcome.

In the mean time, it is possible to simulate this behaviour by creating a handlebars bound helper that returns a higher order function:

makeAction.js
1
2
3
4
5
6
7
8
9
10
11
Ember.Handlebars.registerBoundHelper('makeAction', function(){
  var actionName = arguments[0],
      args = Array.prototype.slice.call(arguments, 1, -1),
      self = this;

  args.unshift(actionName);

  return function() {
    return self.send.apply(self, args);
  };
});

A parent component can then yield this to a child component:

Here is a jsbin that shows this in action.

Parent Component calling Children

A child component calling a parent is fairly straight forward and is well laid out but what about when a parent component wants to call its children?

What do we do if we have a parent that wants its chidren to do something like to zoom to an element or something that is not a reaction to some state mutation? The official ember guidance is you should use the actions up and data down paradigm where you push data changes down to the child components but there are occasions when this does not fit. There are times when you want a child component to do something when no data has changed. You can certainly fudge this but mutating a state change to get a child component to react is pretty awful in my opinion and not something that you should do. This is an occassion when actions up and data down does not go far enough, which I have previously blogged about here and here.

So what do we do?

Subscribe to Parent Events

The first way I solved this was to use Ember.Evented. A component can mix in the Ember.Evented mixin:

people.js
1
2
3
4
5
6
7
8
9
App.XPeopleComponent = Ember.Component.extend(Ember.Evented, {
  actions: {
    callChildren: function() {
      this.trigger('parentCall');
    },

    receiveFromChild: function(person) {
      alert("received " + person.get('name'));
    }

Ember.Evented is mixed into the component and trigger is called on line 3 of the above code sample to broadcast an event to any listening children.

Below is how a child component can subscribe to these events:

child2.js
1
2
3
4
5
6
7
8
9
App.XPersonComponent = Ember.Component.extend({
  _setup: Ember.on('didInsertElement', function(){
    this.get('notifyer').on('parentCall', this, 'onParentCall');
  }),

  onParentCall: function() {
    alert('parent called with ' + this.get('person.name'));
  }
});

Line 3 subscribes to the event and supplies a handler which will be called when the parent triggers the event.

I don’t have a great big problem with this although it is not that nice having to explicitly subscribe via the parent component as opposed to just subscribing to an event.

Here is a jsbin that shows this in action.

Registration with the parent component

Another way of solving this is for the child component to register itself with the parent:

register.js
1
2
3
4
5
6
7
8
9
10
11
App.XPersonComponent = Ember.Component.extend({
  _setup: Ember.on('didInsertElement', function(){
     var parent = this.nearestOfType(App.XPeopleComponent);

     parent.registerChild(this);
  }),

  parentCalling: function() {
    alert('parent called with ' + this.get('person.name'));
  }
});

The child component finds the parent on line 3 of the above and below is the registerChild method that is used to register the child component:

parent2.js
1
2
3
4
5
6
7
App.XPeopleComponent = Ember.Component.extend( {
  children: Ember.A(),

  registerChild: function(child) {
    this.children.pushObject(child);
  }
});

I think this is less favourable because of the very tight coupling. Here is a working jsbin that shows this in action.

Register via Binding

Sam Selikoff offered this way of registering child components via a binding.

First, add a _register method to the component that executes on init:

parent3.js
1
2
3
4
5
6
App.FullscreenMapComponent = Ember.Component.extend({
  _register: function() {
    this.set('register-as', this); // register-as is a new property
  }.on('init')

});

Then, when rendering the component, supply a property to bind register-as to:

Now, the controller has a reference to the component, and can call methods directly on it:

route.js
1
2
3
4
5
6
7
8
App.PinsRoute = Em.Route.extend({
  actions: {
    focusSelectedPin: function() {
      this.controller.get('fullscreenMap').panToSelectedPin();
    }
  }

});

I think this method suffers from the same coupling as the other methods.

So what I want is a way that is less coupled. I want the publisher and the subscriber to know as little about each other as possible. I also do not want to use state mutation as a means of communication.

UPDATE: Somebody mentioned in the comments that a global event bus is anohter way of achieving what is required and I created this post that explains how to achieve this.

clojure’s core.async

I love this quote from the first sentence of this post on the clojure blog about core.async.

There comes a time in all good programs when components or subsystems must stop communicating directly with one another.

The roots of core.async go back to Hoare’s Communicating Sequential Processes (CSP).

I’ve done a good bit of hobby development with clojurescript and I love using core.async channels for communication between components. It can be used synchronously and asynchronously.

With core.async, you create channels which can be thought of as independent threads of activity (I don’t mean threads as in multi-threaded) that can be published to or subscribed to.

I have previously blogged about Handling Mouse Events With core.async.

Core.async has nailed the decoupling of publishers and subscriers via queueable channels.

Epilogue

With core.async the publisher and subscriber know nothing about each other which is really the goal of event based systems and I think both ember and react suffer from the model of handing down functions from parent to child as there is a high degree of coupling.

It would be interesting to see how ember was if we were dealing with channels as a means of communication rather than passing down callable functions.

There is a js-csp library and David Nolen wrote this post about ES6 Generators Deliver Go Style Concurrency.

Ember is going with the data down, actions up meme but there are times when this does not fit and activating state changes as a means of communication can only lead to trouble.

Comments