Parent to Children Component Communication for UI State

Important update: This article was entirely revamped on October 7th, 2015.

It examined how to trigger UI actions from a parent component to child components. I realized the proposed solutions were not conducive to explain the concept right.

I decided to rewrite it in two parts: this, the “Parent to Children Component Communication for UI State” and “Communication Between Distant Components”.

Components are designed to be reusable and loosely coupled.

How do they communicate with one another? Through the interface they expose to the outer world.

{{!-- sample component --}}
{{my-component item=model prop=height action='zoom'}}

The official recommendation is actions up, data down. What does this mean?

Actions flow upward where models’ state is modified and these updates percolate back down. As components are bound to models’ data they react accordingly.

In our sample component, the zoom action could bubble up to the route. There, a new model can be loaded, eventually re-rendering the component passing in that new model.

As you notice, zoom and model are both supplied to the component through its interface. Actions up, data down.

What if the state that changes is UI-related (not model state)?

Approach #1

Let’s introduce a sample app to illustrate the problem.

The parent (list) needs to communicate to its children (list items) to toggle their height. This is a user interface concern, not a data model concern.

And our basic code so far:

// application route

export default Ember.Route.extend({
  model: function() {
    // normally we'd retrieve these models from a service
    return ['red', 'blue', 'green'].map(function(color) {
      return Ember.Object.create({ color: color })
    });
  }
});

// parent item component

export default Ember.Component.extend();

// child item component

export default Ember.Component.extend();
{{!-- application template --}}

<h1>Parent-child communication</h1>
{{parent-item items=model }}

{{!-- parent item component --}}

<button {{action "click"}}>toggle item height</button>
<ul>
  {{#each items as |item|}}
    {{ child-item item=item }}
  {{/each}}
</ul>

{{!-- child item component --}}

<li>{{item.color}}</li>

How do we communicate a height change to the child items?

In the same spirit of passing data down we will pass UI-properties down. The childHeight property on the parent will mirror the height property in the children.

Let’s do that when we register the component. As well as updating our child to toggle that CSS style (or class in a real-world app).

{{!-- parent item component --}}

<button {{action "click"}}>toggle item height</button>
<ul>
  {{#each items as |item|}}
    {{ child-item item=item height=childHeight }}
  {{/each}}
</ul>

{{!-- child item component --}}

<li style="{{if height 'height: 100px;'}}">{{item.color}}</li>

For this to actually work, we need to toggle the parent childHeight upon click:

// parent item component

export default Ember.Component.extend({  
  actions: {
    click() {
      this.toggleProperty('childHeight');
    }
  }
});

Done!

What if… we need finer control over changes to the children?

Approach #2

Based on a method suggested by commenter Restuta, this approach is slightly more complex than the previous one – but gives us more power.

We’ll use a particular parent property to communicate both model- and UI-data down to the children. This is a computed property that wraps everything into one “proxy” object.

Now looking at some code:

// parent item component

export default Ember.Component.extend({

  childWrappers: Ember.computed('items.@each', function() {
    return this.get('items').map(item => {
      return Ember.Object.create({ // wrapper object
        item: item,
        height: false
      });
    });
  }),

  actions: {
    click() {
      this.get('childWrappers').forEach(wrapper => {
        wrapper.toggleProperty('height');
      });
    }
  }
});

Notice that, unlike approach #1, the click() handler is applying properties directly on the child (via its wrapper).

The template will now iterate over childWrappers:

{{!-- parent item component --}}

<button {{action "click"}}>toggle item height</button>
<ul>
  {{#each childWrappers as |wrapper|}}
    {{ child-item item=wrapper.item height=wrapper.height }}
  {{/each}}
</ul>

Presto! This approach gives us much more control. Here a Twiddle for you to experience the magic:

Communicating between distant components

Sometimes components are not in the same hierarchy (like our parent -> children) but still need to send messages to each other.

We explore potential solutions to this problem in our next guide, Communication Between Distant Components.

Enjoyed this article? Don't miss my next one!

Leave me your e-mail for content that will help you master Ember:

Do you want to master Ember fast?

Leave me your e-mail for helpful updates delivered straight to your inbox.

(A few e-mails per month. No BS. Unsubscribe anytime!)