Using Services and Messages to Share Data Between Controllers in AngularJS
I recently wrote a blog post with some basic lessons learned after using AngularJS for a while. In it I briefly described a pattern of using broadcast messages with services to share data between controllers in AngularJS. This method can be useful when you have multiple controllers on a page, some of which need to know when data managed by another controller has been changed. I was asked if there were any examples online. I have since posted an example to GitHub. I will explain the example in a little more detail here.
This example consists of an index.html file, two JavaScript files (one for the controllers and one for the service), and a basic CSS file. You can just open the index.html file in a browser to see it work. The page consists of 3 divs, each controlled by its own controller, with all controllers sharing data via the service.
The HTML is fairly simple:
<div data-ng-controller="TopController"> Top Value: <input data-ng-model="topValue" /><br/> Middle Value: {{middleValue}}<br/> Bottom Value: {{bottomValue}} </div> <div data-ng-controller="MiddleController"> Top Value: {{topValue}}<br/> Middle Value: <input data-ng-model="middleValue" /><br/> Bottom Value: {{bottomValue}} </div> <div data-ng-controller="BottomController"> Top Value: {{topValue}}<br/> Middle Value: {{middleValue}}<br/> Bottom Value: <input data-ng-model="bottomValue" /><br/> </div>
Here we have three divs. Each is bound to its own controller. Each controller is responsible for managing one piece of data. However, each controller wants to display the values handled by the other two. How do we do that?
Let’s start by taking a look at the service. We create this service to hold our values and announce to the app that they have changed.
angular.module('demoService', []).factory('DemoService', function($rootScope){ var service = {}; service.topValue = 0; service.middleValue = 0; service.bottomValue = 0; service.updateTopValue = function(value){ this.topValue = value; $rootScope.$broadcast("valuesUpdated"); } service.updateMiddleValue = function(value){ this.middleValue = value; $rootScope.$broadcast("valuesUpdated"); } service.updateBottomValue = function(value){ this.bottomValue = value; $rootScope.$broadcast("valuesUpdated"); } return service; });
This service is really just a JavaScript object that lives outside of any one scope. It is setup as an Angular module so that it can be wired into your app module (see the code for how this is done, as it’s outside the scope of this discussion). This object contains variables for all three of our values, and update methods for each one. Note that I call these “updaters” and not “setters”. They do not follow the typical setter pattern in that they do more work than just set a value. These methods set the value, and then send out a broadcast message announcing to the world that a value has changed.
Next, let’s take a look at one of the controllers.
function TopController($scope, DemoService) { $scope.topValue = 0; $scope.middleValue = 0; $scope.bottomValue = 0; $scope.$watch('topValue', function() { DemoService.updateTopValue($scope.topValue); }); $scope.$on('valuesUpdated', function() { $scope.middleValue = DemoService.middleValue; $scope.bottomValue = DemoService.bottomValue; }); }
This is the controller for our top div. Notice we have wired in our DemoService. This gives us access to it in the controller, just like a standard AngularJS service (such as $scope, $location, etc). We use $scope’s $watch method to watch the value we care about. This is just the easiest for this demo. You can process the changed value however you like (perhaps with ng-click or ng-change, or in the middle of some other method that executes after some business logic — it really depends on your app). All that our $watch method does is call the updater method on our service.
The next section is the $on method that listens for the broadcast message. Remember, this message will get broadcast every time a value in the service is updated. When the message is fired, each controller will receive it and update the values it cares to by getting them from the service.
That is all there is to it. Hopefully this helps organize your inner-controller data sharing in AngularJS!
That’s what I have been looking for. Here I have created a pluker of this example http://plnkr.co/edit/BMcyauHwrdKpOlGdcPOX
Only one question though, when I import the routine into my existing app, it breaks the whole program. I have copied all the code in my controller and service files, and have changed
var demo = angular.module(‘demo’, [‘demoService’]);
to var demo = angular.module(‘MyAppName’, [‘demoService’]);
But it just breaks the app. Do you know what I am missing? Thank you.
Thanks the reply, Question. I assume “MyAppName” is your ng-app? aka:
This works for me. In the example above, I changed the ng-app value to “MyAppName”, and changed the line you describe to:
var demo = angular.module(‘MyAppName’, [‘demoService’]);
And everything still works. Can you give me more information on what your issue is?
Thanks,
Jon
What are the performance implications of using $rootScope$broadcast? I’ve read that the event bubbles throughout all available scopes. Is this a big hit in performance, and if so at what size application would you really need to worry about the performance implications of using $broadcast?
You can use $emit instead, which bubbles up starting with the sender. Since the sender is $rootScope and $rootScope has no parents, you can still listen on $rootScope without the performance hit.
Good article.
Another approach is to not replicate the data to the individual controllers. Since the data is technically the same shared information, you can keep it in an “app” level controller, and have each controller reference that (through inheritance)
The service won’t maintain the data, rather, you pass to the service the object in the App controller’s scope so that it can operate on it or manipulate it any way the service chooses.
Since data is not replicated, but rather shared, the obvious advantage of this approach is you remove the need to call $broadcast and $on methods.
I could have stored the data in $rootScope but I chose to store it in an app-level controller’s scope instead. More of a personal preference on that one I think.
Here’s a forked version from the original plunk: http://plnkr.co/Ch1yJS
controllers:
function AppController($scope, DemoService) {
$scope.sharedData = {
topValue: 0,
middleValue: 0,
bottomValue: 0
};
}
function TopController($scope, DemoService) {
$scope.$watch(‘topValue’, function() {
DemoService.updateTopValue($scope.sharedData, $scope.topValue);
});
}
function MiddleController($scope, DemoService) {
$scope.$watch(‘middleValue’, function() {
DemoService.updateMiddleValue($scope.sharedData, $scope.middleValue);
});
}
function BottomController($scope, DemoService) {
$scope.$watch(‘bottomValue’, function() {
DemoService.updateBottomValue($scope.sharedData, $scope.bottomValue);
});
}
service:
angular.module(‘demoService’, []).factory(‘DemoService’, function(){
var updateTopValue = function(sharedData, value){
sharedData.topValue = value;
}
var updateMiddleValue = function(sharedData, value){
sharedData.middleValue = value;
}
var updateBottomValue = function(sharedData, value){
sharedData.bottomValue = value;
}
return {
updateTopValue: updateTopValue,
updateMiddleValue: updateMiddleValue,
updateBottomValue: updateBottomValue
};
});
var demo = angular.module(‘demo’, [‘demoService’]);
html:
Controller Messaging Demo
Top Value:
Middle Value: {{sharedData.middleValue}}
Bottom Value: {{sharedData.bottomValue}}
Top Value: {{sharedData.topValue}}
Middle Value:
Bottom Value: {{sharedData.bottomValue}}
Top Value: {{sharedData.topValue}}
Middle Value: {{sharedData.middleValue}}
Bottom Value:
Im new to Angular, but I think it is possible to semplify the code, eliminating the need of broadcast:
http://jsfiddle.net/y7sdS/5/
Isn’t that right?
Yes, that will work in that case because you’ve wrapped the data in an object, and you’re referencing the object. That won’t work for primitive values, however.
One issue I have with that is your not encapsulating your data. Of course, neither am I in my example. But one thing I’ve learned over the months since writing this, is that it’s a good practice to get in the habit of, for all the same reasons that it’s a good practice in back end languages.
Have you considered having the service watch it’s own models, then on change broadcast? Personally I go back and forth on which I prefer, but it allows controller access the data directly, without the wrapper method. The controllers then just listen like they currently do.
If my data is coming from a JSON file, how do I watch for changes between two different controllers?
Would it be possible to have the service expose a listen function to eliminate the need for the $scope.on?
Many thanks!, this has been very useful for me.
Is well known that this kind of procedure with $emit and $broadcast has a heavy impact in app’s performance, so react js expose a better way to handle this
Very useful post !
http://blog.logiticks.com/communication-between-controllers-in-angularjs-emitonbroadcast/
To share data among controllers we can use services and we can do the same by using $emit and $broadcast also. what is the difference b/w both the cases ?