Page Banner

Communicating between controllers in AngularJS

AngularJS is what HTML would have been, had it been designed for building web-apps.

is what we see on the AngularJS homepage, and with the features that it provides, it lives up to those words. But learning Angular, if you have not faced some problem or odd behavior with scopes there’s a pretty good chance you’re doing something wrong. The great part about Angular JS is that it does not take over your web-app. It is client-side only. It doesn’t exist on the server. It’s all browser stuff. Angular’s way of separating concerns for Model-View-Controllers is pretty clever, however that is also a stumbling block for most newbies.

So we’ve successfully fiddled about with our first few applications with single controllers and want to try out something a bit more complex. Complexity requires logic, and to be smart about it, we’re keeping different kinds of logic in separate controllers. But since at an abstract level, it is a single application performing multiple functions (within multiple controllers) our controllers must have some way to communicate among themselves, so we try to bind data to models using the “ng-model” directive and give it a try:

HTML:
<div ng-controller=”controllerOne”
 <input ng-model=”someModel”/>
</div>
<div ng-controller=”controllerTwo”
 {{someModel}}
</div>
JS:
angular.module('exampleModule',[])
.controller('controllerOne',['$scope', function($scope){
 //some logic here
 var data_one = $scope.someModel;
 //some logic here
}])
.controller('controllerTwo',['$scope', function($scope){
 //some logic here
 var data_two = $scope.someModel;
 //some logic here
}]);

But we observe that data in the model is not being shared among the controllers. The reason for this is due to the prorototypical inheritance in Javascript, i.e. an object inherits from another object. A very detailed explaination can be found here: https://github.com/angular/angular.js/wiki/Understanding-Scopes. Basically, every time the Angular JS parser encounters the “ng-controller” directive, it creates a new instance of that controller, having all the properties of the controller object but in a new scope.

So, now we get that we have to somehow copy the state of the scope of one controller to a new scope of another controller. The how to that lies in the singletons of Angular JS, namely: Services, factories, providers and $rootScope.

Services, factories and providers are pretty similar and a good explanation can be found in this post: http://allenhwkim.tumblr.com/post/69895172775/service-factory-and-provider-whats-difference.

Our strategy to use a singleton now is to broadcast an event occurrence flag from a service across the RootScope with “$rootScope.$broadcast(‘someEvent’)” and force all controllers to listen for that event using “$rootScope.$on(‘someEvent’, function($scope){//event handling logic;})” and because the service has only one instance of $scope, we make sure all the controllers listen to the same instance of the event:

HTML:
<div ng-controller=”controllerOne”
 <input ng-model=”someModel”/ ng-click=”someAction(someModel)”>
</div>
<div ng-controller=”controllerTwo”
 {{someModel}}
</div>
JS:
angular.module('exampleModule',[])
.controller('controllerOne',['$scope', ourService, function($scope, ourService){
 //some logic here
 var data_one = $scope.someModel;
 $scope.someAction = function(obj){
 ourService.toBroadcast(obj);
 }
 //some logic here
 $rootScope.$on('someEvent', function($scope){//event handling logic;});
}])
.controller('controllerTwo',['$scope', ourService, function($scope, ourService){
 //some logic here
 var data_two = $scope.someModel;
 //some logic here
 $rootScope.$on('someEvent', function($scope){//event handling logic;});
}])
.service('ourService', function(obj){
 var ourService = {};
 ourService.someVariable = {}; //can be a primitive or a complex data type
 ourService.toBroadcast = function(obj){
 this.someVariable = obj;
 $rootScope.$broadcast('someEvent'); 
 }
 return ourService;
})

We’re set. Our model is being successfully passed from one controller to another. But why is the view not updating then?

Angular JS two-way data binding (the $digest loop) roughly works as follows:

  • When referencing ‘someEvent’ in the view, angular creates a “$watch” for the $scope.someEvent property.
  • Upon events occurrence in the angular domain, all watches will be evaluated and will be compared to their previous values.
  • If a value has changed (i.e. the previous value is not equal to the current value), changes in the value are propagated to the watching parties – e.g. the view in this case, causing an update of the UI.

So given this machinery, why hasn’t our view been updated with the values in the model? That would be because ‘$scope.someEvent’ property updated in the ‘$scope.$on()’ of the controllers does not reference the ‘ourService.someEvent’ but makes a copy of it in its scope (i.e the current scope of the controller) and that value is not reflected in the other scope that is in play in our view. And this again is due to the prototypical inheritance in Javascript, so we’ve come full-circle. To break away from this loop, we’ll bind our service values to the controllers by manually adding the $watch directive which will update our scopes variables upon change of the watched value:

HTML:
<div ng-controller=”controllerOne”
 <input ng-model=”someModel”/ ng-click=”someAction(someModel)”>
</div>
<div ng-controller=”controllerTwo”
 {{someModel}}
</div>
JS:
angular.module('exampleModule',[])
.controller('controllerOne',['$scope', ourService, function($scope, ourService){
 //some logic here
 var data_one = $scope.someModel;
 $scope.someAction = function(obj){
 ourService.toBroadcast(obj);
 }
 //some logic here
 $rootScope.$on('someEvent', function($scope){//event handling logic;});
 $scope.$watch(
 function(){
 return this.someModel = ourService.someVariable;
 },
 function(newVal){
 $scope.someModel = newVal;
 }
 )
}])
.controller('controllerTwo',['$scope', ourService, function($scope, ourService){
 //some logic here
 var data_two = $scope.someModel;
 //some logic here
 $rootScope.$on('someEvent', function($scope){//event handling logic;});
 $scope.$watch(
 function(){
 return this.someModel = ourService.someVariable;
 },
 function(newVal){
 $scope.someModel = newVal;
 }
 )
}])
.service('ourService', function(obj){
 var ourService = {};
 ourService.someVariable = {}; //can be a primitive or a complex data type
 ourService.toBroadcast = function(obj){
 this.someVariable = obj;
 $rootScope.$broadcast('someEvent'); 
 }
 return ourService;
})

And with that, our view is updated with the changes and we’ve achieved communication among our controllers!