Sharing Data Between Controllers? Best Practice: Use a Service

By Dave Ceddia

Angular started off nice and easy. Magical, even. “Two-way binding! Wow!”

And you trotted off and started building your masterpiece, until you hit a snag: you’re building standalone components like everyone online suggests, but how do you share data between those components?

Maybe you have 2 views in separate routes that need access to some status variable. Or you have 3 separate components that all need access to the same piece of data.

What’s the best way to share it? Some sort of crazy controller-inheritance scheme?

No, of course not. The simple, easy way is to use a service.

The Problem

Let’s say you have 2 panes, side-by-side, each represented by a directive.

Two Panes

Here’s the code for Pane 1:

angular.directive('paneOne', function() {
  return {
    restrict: 'E',
    scope: {},
    template: [
      '<div>',
        '<input ng-model="p1.text">',
        '<button ng-click="p1.addToList()">Add To List</button>',
      '</div>'
    ].join(''),
    controllerAs: 'p1',
    controller: function() {
      var vm = this;
      vm.text = "";
      vm.addToList = function() {
        // TODO: add to the list in Pane 2 somehow
        vm.text = "";
      };
    }
  };
});

And for Pane 2:

angular.directive('paneTwo', function() {
  return {
    restrict: 'E',
    scope: {},
    template: [
      '<ul>',
        '<li ng-repeat="item in p2.listItems">{{ item }}</li>',
      '</ul>'
    ].join(''),
    controllerAs: 'p2',
    controller: function() {
      var vm = this;
      // TODO: get this list of items from Pane 1 somehow
      vm.listItems = [];
    }
  };
});

We want to be able to type something into the input box in Pane 1, click “Add To List”, and have it appear in Pane 2’s list.

Create a Service to Hold Shared State

In order to share data between 2 or more controllers, create a service that acts as a mediator. This keeps the controllers (or components) loosely-coupled: they don’t need to know about each other, they just need to know about the data source – your service.

angular.factory('sharedList', function() {
  var list = [];

  return {
    addItem: addItem,
    getList: getList
  };

  function addItem(item) {
    list.push(item);
  }

  function getList() {
    return list;
  }
});

This service is super simple. Call addItem to put things in the list, and getList to retrieve the whole list. It’s so simple, it doesn’t even support removing or clearing items. That’s how simple this thing is.

Inject That Service Everywhere That Cares

Now that we have our service, we need to inject it everywhere that needs to access or modify the data.

Start with Pane 1’s controller:

// Inject sharedList
controller: function(sharedList) {
  var vm = this;
  vm.text = "";
  vm.addToList = function() {
    // Stuff the item into the shared list
    sharedList.addItem(vm.text);
    vm.text = "";
  };
}

Now Pane 2’s controller, to read the data:

// Inject sharedList
controller: function(sharedList) {
  var vm = this;
  // Read the data
  vm.listItems = sharedList.getList();
}

No Watchers Though?

As I was writing this, I was pretty sure that the list in Pane 2 would not automatically update until I added some watchers.

But then, I put the code into JSBin, and … lo and behold, it works! Why?

  1. The ng-repeat sets up a watcher on the array
  2. Clicking “Add To List” triggers a digest cycle, which re-evaluates the ng-repeat’s watcher.
  3. Because sharedData.getList() returns a reference to an array, the watcher sees that p2.listItems has changed. This is crucial: if getList returned a different array than the one modified by addToList, this would not work.

So: This communication method may work perfectly fine without watchers. But if you find that it isn’t, check how you’re passing data around. You may need to explicitly watch for changes.

Recap

  1. Create a service to contain your data. Give it getter and setter methods.
  2. Inject that service anywhere that needs the data.
  3. That’s pretty much it (unless you need watchers – in which case, add them).

Want to learn best-practices Angular development, as well as get a head start on Angular 2, ES6, and TypeScript? Sign up for my newsletter below!

Thanks for reading.