What's the correct way to communicate between controllers?
I'm currently using a horrible fudge involving window
:
function StockSubgroupCtrl($scope, $http) {
$scope.subgroups = [];
$scope.handleSubgroupsLoaded = function(data, status) {
$scope.subgroups = data;
}
$scope.fetch = function(prod_grp) {
$http.get('/api/stock/groups/' + prod_grp + '/subgroups/').success($scope.handleSubgroupsLoaded);
}
window.fetchStockSubgroups = $scope.fetch;
}
function StockGroupCtrl($scope, $http) {
...
$scope.select = function(prod_grp) {
$scope.selectedGroup = prod_grp;
window.fetchStockSubgroups(prod_grp);
}
}
https://i.stack.imgur.com/ti7Ph.png
So, now you can:
use $broadcast from the $rootScope
listen using $on from the local $scope that needs to know about the event
Original Answer Below
I highly advise not to use $rootScope.$broadcast
+ $scope.$on
but rather $rootScope.$emit
+ $rootScope.$on
. The former can cause serious performance problems as raised by @numan. That is because the event will bubble down through all scopes.
However, the latter (using $rootScope.$emit
+ $rootScope.$on
) does not suffer from this and can therefore be used as a fast communication channel!
From the angular documentation of $emit
:
Dispatches an event name upwards through the scope hierarchy notifying the registered
Since there is no scope above $rootScope
, there is no bubbling happening. It is totally safe to use $rootScope.$emit()
/ $rootScope.$on()
as an EventBus.
However, there is one gotcha when using it from within Controllers. If you directly bind to $rootScope.$on()
from within a controller, you'll have to clean up the binding yourself when your local $scope
gets destroyed. This is because controllers (in contrast to services) can get instantiated multiple times over the lifetime of an application which would result into bindings summing up eventually creating memory leaks all over the place :)
To unregister, just listen on your $scope
's $destroy
event and then call the function that was returned by $rootScope.$on
.
angular
.module('MyApp')
.controller('MyController', ['$scope', '$rootScope', function MyController($scope, $rootScope) {
var unbind = $rootScope.$on('someComponent.someCrazyEvent', function(){
console.log('foo');
});
$scope.$on('$destroy', unbind);
}
]);
I would say, that's not really an angular specific thing as it applies to other EventBus implementations as well, that you have to clean up resources.
However, you can make your life easier for those cases. For instance, you could monkey patch $rootScope
and give it a $onRootScope
that subscribes to events emitted on the $rootScope
but also directly cleans up the handler when the local $scope
gets destroyed.
The cleanest way to monkey patch the $rootScope
to provide such $onRootScope
method would be through a decorator (a run block will probably do it just fine as well but pssst, don't tell anybody)
To make sure the $onRootScope
property doesn't show up unexpected when enumerating over $scope
we use Object.defineProperty()
and set enumerable
to false
. Keep in mind that you might need an ES5 shim.
angular
.module('MyApp')
.config(['$provide', function($provide){
$provide.decorator('$rootScope', ['$delegate', function($delegate){
Object.defineProperty($delegate.constructor.prototype, '$onRootScope', {
value: function(name, listener){
var unsubscribe = $delegate.$on(name, listener);
this.$on('$destroy', unsubscribe);
return unsubscribe;
},
enumerable: false
});
return $delegate;
}]);
}]);
With this method in place the controller code from above can be simplified to:
angular
.module('MyApp')
.controller('MyController', ['$scope', function MyController($scope) {
$scope.$onRootScope('someComponent.someCrazyEvent', function(){
console.log('foo');
});
}
]);
So as a final outcome of all this I highly advise you to use $rootScope.$emit
+ $scope.$onRootScope
.
Btw, I'm trying to convince the angular team to address the problem within angular core. There's a discussion going on here: https://github.com/angular/angular.js/issues/4574
Here is a jsperf that shows how much of a perf impact $broadcast
brings to the table in a decent scenario with just 100 $scope
's.
http://jsperf.com/rootscope-emit-vs-rootscope-broadcast
https://i.stack.imgur.com/FQ9dE.png
The top answer here was a work around from an Angular problem which no longer exists (at least in versions >1.2.16 and "probably earlier") as @zumalifeguard has mentioned. But I'm left reading all these answers without an actual solution.
It seems to me that the answer now should be
use $broadcast from the $rootScope
listen using $on from the local $scope that needs to know about the event
So to publish
// EXAMPLE PUBLISHER
angular.module('test').controller('CtrlPublish', ['$rootScope', '$scope',
function ($rootScope, $scope) {
$rootScope.$broadcast('topic', 'message');
}]);
And subscribe
// EXAMPLE SUBSCRIBER
angular.module('test').controller('ctrlSubscribe', ['$scope',
function ($scope) {
$scope.$on('topic', function (event, arg) {
$scope.receiver = 'got your ' + arg;
});
}]);
Plunkers
Regular $scope syntax (as you see above)
new Controller As syntax
If you register the listener on the local $scope
, it will be destroyed automatically by $destroy
itself when the associated controller is removed.
controllerAs
syntax? I was able to use $rootScope
in the subscriber to listen for the event, but I was just curious if there was a different pattern.
$scope
explicitly. John Papa writes about events being one "exception" to his usual rule of keeping $scope
"out" of his controllers (I use quotes because as he mentions Controller As
still has $scope
, it's just under the bonnet).
controller as
syntax alternative as requested. I hope that's what you meant.
Using $rootScope.$broadcast and $scope.$on for a PubSub communication.
Also, see this post: AngularJS – Communicating Between Controllers
$rootScope
and $watch
. Not sure that's any improvement.
Since defineProperty has browser compatibility issue, I think we can think about using a service.
angular.module('myservice', [], function($provide) {
$provide.factory('msgBus', ['$rootScope', function($rootScope) {
var msgBus = {};
msgBus.emitMsg = function(msg) {
$rootScope.$emit(msg);
};
msgBus.onMsg = function(msg, scope, func) {
var unbind = $rootScope.$on(msg, func);
scope.$on('$destroy', unbind);
};
return msgBus;
}]);
});
and use it in controller like this:
controller 1 function($scope, msgBus) { $scope.sendmsg = function() { msgBus.emitMsg('somemsg') } }
controller 2 function($scope, msgBus) { msgBus.onMsg('somemsg', $scope, function() { // your logic }); }
GridLinked posted a PubSub solution which seems to be designed pretty well. The service can be found, here.
Also a diagram of their service:
https://i.stack.imgur.com/m5LHL.png
Actually using emit and broadcast is inefficient because the event bubbles up and down the scope hierarchy which can easily degrade into performance bottlement for a complex application.
I would suggest to use a service. Here is how I recently implemented it in one of my projects - https://gist.github.com/3384419.
Basic idea - register a pubsub/event bus as a service. Then inject that eventbus where ever you need to subscribe or publish events/topics.
$rootScope.$emit()
only bubbles upwards. However, as there is no scope above the $rootScope
there is nothing to be afraid of. So if you are just using $rootScope.$emit()
and $rootScope.$on()
you will have a fast system wide EventBus.
$rootScope.$on()
inside your controller, you will need to clean up the event binding as otherwise they will sum up as it's creating a new one each time the controller is instantiated and they don't get automatically destroyed for you since you are binding to $rootScope
directly.
Using get and set methods within a service you can passing messages between controllers very easily.
var myApp = angular.module("myApp",[]);
myApp.factory('myFactoryService',function(){
var data="";
return{
setData:function(str){
data = str;
},
getData:function(){
return data;
}
}
})
myApp.controller('FirstController',function($scope,myFactoryService){
myFactoryService.setData("Im am set in first controller");
});
myApp.controller('SecondController',function($scope,myFactoryService){
$scope.rslt = myFactoryService.getData();
});
in HTML HTML you can check like this
<div ng-controller='FirstController'>
</div>
<div ng-controller='SecondController'>
{{rslt}}
</div>
Regarding the original code - it appears you want to share data between scopes. To share either Data or State between $scope the docs suggest using a service:
To run stateless or stateful code shared across controllers — Use angular services instead.
To instantiate or manage the life-cycle of other components (for example, to create service instances).
I've actually started using Postal.js as a message bus between controllers.
There are lots of benefits to it as a message bus such as AMQP style bindings, the way postal can integrate w/ iFrames and web sockets, and many more things.
I used a decorator to get Postal set up on $scope.$bus
...
angular.module('MyApp')
.config(function ($provide) {
$provide.decorator('$rootScope', ['$delegate', function ($delegate) {
Object.defineProperty($delegate.constructor.prototype, '$bus', {
get: function() {
var self = this;
return {
subscribe: function() {
var sub = postal.subscribe.apply(postal, arguments);
self.$on('$destroy',
function() {
sub.unsubscribe();
});
},
channel: postal.channel,
publish: postal.publish
};
},
enumerable: false
});
return $delegate;
}]);
});
Here's a link to a blog post on the topic...
http://jonathancreamer.com/an-angular-event-bus-with-postal-js/
This is how I do it with Factory / Services and simple dependency injection (DI).
myApp = angular.module('myApp', [])
# PeopleService holds the "data".
angular.module('myApp').factory 'PeopleService', ()->
[
{name: "Jack"}
]
# Controller where PeopleService is injected
angular.module('myApp').controller 'PersonFormCtrl', ['$scope','PeopleService', ($scope, PeopleService)->
$scope.people = PeopleService
$scope.person = {}
$scope.add = (person)->
# Simply push some data to service
PeopleService.push angular.copy(person)
]
# ... and again consume it in another controller somewhere...
angular.module('myApp').controller 'PeopleListCtrl', ['$scope','PeopleService', ($scope, PeopleService)->
$scope.people = PeopleService
]
I liked the way how $rootscope.emit
was used to achieve intercommunication. I suggest the clean and performance effective solution without polluting global space.
module.factory("eventBus",function (){
var obj = {};
obj.handlers = {};
obj.registerEvent = function (eventName,handler){
if(typeof this.handlers[eventName] == 'undefined'){
this.handlers[eventName] = [];
}
this.handlers[eventName].push(handler);
}
obj.fireEvent = function (eventName,objData){
if(this.handlers[eventName]){
for(var i=0;i<this.handlers[eventName].length;i++){
this.handlers[eventName][i](objData);
}
}
}
return obj;
})
//Usage:
//In controller 1 write:
eventBus.registerEvent('fakeEvent',handler)
function handler(data){
alert(data);
}
//In controller 2 write:
eventBus.fireEvent('fakeEvent','fakeData');
Here's the quick and dirty way.
// Add $injector as a parameter for your controller
function myAngularController($scope,$injector){
$scope.sendorders = function(){
// now you can use $injector to get the
// handle of $rootScope and broadcast to all
$injector.get('$rootScope').$broadcast('sinkallships');
};
}
Here is an example function to add within any of the sibling controllers:
$scope.$on('sinkallships', function() {
alert('Sink that ship!');
});
and of course here's your HTML:
<button ngclick="sendorders()">Sink Enemy Ships</button>
$rootScope
?
Starting angular 1.5 and it's component based development focus. The recommended way for components to interact is through the use of the 'require' property and through property bindings (input/output).
A component would require another component (for instance the root component) and get a reference to it's controller:
angular.module('app').component('book', {
bindings: {},
require: {api: '^app'},
template: 'Product page of the book: ES6 - The Essentials',
controller: controller
});
You can then use the methods of the root component in your child component:
$ctrl.api.addWatchedBook('ES6 - The Essentials');
This is the root component controller function:
function addWatchedBook(bookName){
booksWatched.push(bookName);
}
Here is a complete architectual overview: Component Communications
You can access this hello function anywhere in the module
Controller one
$scope.save = function() {
$scope.hello();
}
second controller
$rootScope.hello = function() {
console.log('hello');
}
I will create a service and use notification.
Create a method in the Notification Service Create a generic method to broadcast notification in Notification Service. From source controller call the notificationService.Method. I also pass the corresponding object to persist if needed. Within the method, I persist data in the notification service and call generic notify method. In destination controller I listen ($scope.on) for the broadcast event and access data from the Notification Service.
As at any point Notification Service is singleton it should be able to provide persisted data across.
Hope this helps
You can use AngularJS build-in service $rootScope
and inject this service in both of your controllers. You can then listen for events that are fired on $rootScope object.
$rootScope provides two event dispatcher called $emit and $broadcast
which are responsible for dispatching events(may be custom events) and use $rootScope.$on
function to add event listener.
You should use the Service , because $rootscope
is access from whole Application , and it increases the load, or youc use the rootparams if your data is not more.
function mySrvc() {
var callback = function() {
}
return {
onSaveClick: function(fn) {
callback = fn;
},
fireSaveClick: function(data) {
callback(data);
}
}
}
function controllerA($scope, mySrvc) {
mySrvc.onSaveClick(function(data) {
console.log(data)
})
}
function controllerB($scope, mySrvc) {
mySrvc.fireSaveClick(data);
}
You can do it by using angular events that is $emit and $broadcast. As per our knowledge this is the best, efficient and effective way.
First we call a function from one controller.
var myApp = angular.module('sample', []);
myApp.controller('firstCtrl', function($scope) {
$scope.sum = function() {
$scope.$emit('sumTwoNumber', [1, 2]);
};
});
myApp.controller('secondCtrl', function($scope) {
$scope.$on('sumTwoNumber', function(e, data) {
var sum = 0;
for (var a = 0; a < data.length; a++) {
sum = sum + data[a];
}
console.log('event working', sum);
});
});
You can also use $rootScope in place of $scope. Use your controller accordingly.
Success story sharing