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.
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() {
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;
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:
controller: function(sharedList) {
var vm = this;
vm.text = "";
vm.addToList = function() {
sharedList.addItem(vm.text);
vm.text = "";
};
}
Now Pane 2’s controller, to read the data:
controller: function(sharedList) {
var vm = this;
vm.listItems = sharedList.getList();
}
See it working on JSBin.
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?
- The
ng-repeat
sets up a watcher on the array - Clicking “Add To List” triggers a digest cycle, which re-evaluates the
ng-repeat
’s watcher. - 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
- Create a service to contain your data. Give it getter and setter methods.
- Inject that service anywhere that needs the data.
- 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.
Sharing Data Between Controllers? Best Practice: Use a Service was originally published by Dave Ceddia at Angularity on December 04, 2015.
CodeProject