How to pass data between sibling components without using $scope?

Beslinda N. picture Beslinda N. · Mar 16, 2016 · Viewed 45.4k times · Source

I am making a component that contains 3 child components in this way:

<header-component>
<side-component>
<main-component>

The main component contains list of heroes. The header component contains two buttons that are suppose to switch the view on the main component to list or grid view.

The problem I have now is passing data from the header-component to the main component. So when I click grid button the view on the main content should change to grid view , same for the row view.

How can the data be passed between child components in angular 1.5 ?

Answer

dfsq picture dfsq · Mar 16, 2016

Component approach

I would suggest you to align with Angular 2 component approach and use inputs/outputs approach. If you do so, you will be able to easily migrate to Angular 2, because components will be conceptually identical (with difference only in syntax). So here is the way you do it.

So we basically want header and main components to share piece of state with header to be able to change it. There are several approaches we can use to make it work, but the simplest is to make use of intermediate parent controller property. So let's assume parent controller (or component) defines this view property you want to be used by both header (can read and modify) and main (can read) components.

Header component: input and output.

Here is how simple header component could look like:

.component('headerComponent', {
  template: `
    <h3>Header component</h3>
    <a ng-class="{'btn-primary': $ctrl.view === 'list'}" ng-click="$ctrl.setView('list')">List</a>
    <a ng-class="{'btn-primary': $ctrl.view === 'table'}" ng-click="$ctrl.setView('table')">Table</a>
  `,
  controller: function() {
    this.setView = function(view) {
      this.view = view
      this.onViewChange({$event: {view: view}})
    }
  },
  bindings: {
    view: '<',
    onViewChange: '&'
  }
})

The most important part here is bindings. With view: '<' we specify that header component will be able to read outer something and bind it as view property of the own controller. With onViewChange: '&' components defined outputs: the channel for notifying/updating outer world with whatever it needs. Header component will push some data through this channel, but it doesn't know what parent component will do with it, and it should not care.

So it means that header controller can be used something like

<header-component view="root.view" on-view-change="root.view = $event.view"></header-component> 

Main component: input.

Main component is simpler, it only needs to define input it accepts:

.component('mainComponent', {
  template: `
    <h4>Main component</h4>
    Main view: {{ $ctrl.view }}
  `,
  bindings: {
    view: '<'
  }
})

Parent view

And finally it all wired together:

<header-component view="root.view" on-view-change="root.view = $event.view"></header-component>
<main-component view="root.view"></main-component>

Take a look and play with simple demo.

angular.module('demo', [])

.controller('RootController', function() {
  this.view = 'table'
})

.component('headerComponent', {
  template: `
    <h3>Header component</h3>
    <a class="btn btn-default btn-sm" ng-class="{'btn-primary': $ctrl.view === 'list'}" ng-click="$ctrl.setView('list')">List</a>
    <a class="btn btn-default btn-sm" ng-class="{'btn-primary': $ctrl.view === 'table'}" ng-click="$ctrl.setView('table')">Table</a>
  `,
  controller: function() {
    this.setView = function(view) {
      this.view = view
      this.onViewChange({$event: {view: view}})
    }
  },
  bindings: {
    view: '<',
    onViewChange: '&'
  }
})

.component('mainComponent', {
  template: `
    <h4>Main component</h4>
    Main view: {{ $ctrl.view }}
  `,
  bindings: {
    view: '<'
  }
})
<script src="https://code.angularjs.org/1.5.0/angular.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />

<div class="container" ng-app="demo" ng-controller="RootController as root">
  
    <pre>Root view: {{ root.view }}</pre>
    
    <header-component view="root.view" on-view-change="root.view = $event.view"></header-component>
    <main-component view="root.view"></main-component>
    
</div>

Demo: http://plnkr.co/edit/ODuY5Mp9HhbqA31G4w3t?p=info


Here is a blog post I wrote covering component-based design in details: http://dfsq.info/site/read/angular-components-communication