I have two controllers and want to notify one controller that some event has happened from the other controller. For this I am trying to use angular broadcast but have been unsuccessful. Please find below my code:
firstController.js
$rootScope.$on('xyz',function(){
alert('Called');
});
secondController.js
$rootScope.$broadcast('xyz');
Can someone please help in identifying what I am missing here?
Use either a combination of:
$rootScope.$broadcast();
$scope.$on();
// or
$rootScope.$emit();
$rootScope.$on();
$broadcast dispatches the event downward to all child scopes, so you can listen to it with the $scope service.
However, $emit dispatches upward through the scope hierarchy, and since $rootScope is the at the highest level, you can use $rootScope to dispatch and listen to the same event. This is also much better in regards to performance since the event doesn't propagate down through multiple scopes.
Please have a look here: jsfiddle
var app = angular.module('app', []);
app.controller('c1', function($rootScope, $scope){
$scope.click = function() {
$rootScope.$broadcast('xyz');
}
});
app.controller('c2', function($scope){
$scope.$on('xyz', function(){
alert("clicked");
});
})
in firstController.js use $scope instead $rootScope
$scope.$on('xyz', function(event, args) {
alert('Called')
});
Related
Could anyone give me some tips on how to handle the situation when broadcast listeners aren't set before the broadcast event?
I've seen some solutions with $timeout, but they don't feel OK. Thanks!
var app = angular.module('app', []);
app.controller('ParentCtrl',
function ParentCtrl ($scope) {
this.data = 'parent';
$scope.$broadcast('parent', 'Some data'); // event is sent before any listeners are set
});
app.controller('ChildCtrl',
function SiblingOneCtrl ($scope) {
this.data = 'child';
$scope.$on('parent', function (event, data) {
document.write(data); // never activates
});
});
Working example:
http://codepen.io/AndriusRimkus/pen/zqMONm
In your case, you will always try to broadcast before any listener will be subscribed.
That's because you ParentCtrl will always initialized before ChildCtrl.
Try to watch on you controllers like a constructors.
If you what to broadcast something to you child controllers then you need to have some events (like, clicks) to start broadcasting manually, but not from controller directly.
I have a simple question: what's the best ('cleanest', 'scaleable') path one should go when it comes to interact between (let's say) two controllers. Would that be to define a service and watch that service's return-value in order to react?
I setup a simple example here, where I watch the service's current value:
$scope.$watch(
function() {
return myService.getValue();
},
function(newVal) {
$scope.value1 = newVal;
});
and update that service's value when one of the buttons is clicked.
Can this be done better, smaller, cleaner somehow? What's the best practice here?
Cheers.
Use service to share data between controllers
Your case is trying to share data between controllers, rather than watch service's value in controllers, I think directly reference service object to controller's scope is a better way
So your view can be
<pre ng-controller="cntrl1">Value in cntrl1: {{ myService.value }} <button ng-click="update('value1')">Change to 'value1'</button></pre>
<pre ng-controller="cntrl2">Value in cntrl2: {{ myService.value }} <button ng-click="update('value2')">Change to 'value2'</button></pre>
and change your controllers to
app.controller('cntrl1', function(myService, $scope) {
$scope.myService = myService;
$scope.update = function(str) {
$scope.myService.setValue(str);
}
});
app.controller('cntrl2', function(myService, $scope) {
$scope.myService = myService;
$scope.update = function(str) {
$scope.myService.setValue(str);
}
});
Use $broadcast/$emit
Just as #squiroid points out, you can use $broadcast to broadcast events to any controllers who is monitoring targeted events.
Please note here, you'd better not use $rootScope.$broadcast + $scope.$on but rather $rootScope.$emit+ $rootScope.$onas $broadcast event will bubble down through all descendant scopes, which might lead to serious performance problems.
This is the best way to communicate b/w the controller sharing same data via sevice but it is limited b/w controllers having the same service:-
Instead you can also choose to broadcast events that are captured by other controllers and change that data accordingly this way is more scaleable but not clean :-)
Sender ctrl :-
$rootScope.$broadcast('update', 'Some data'); //method that lets pretty much everything hear it even $scope too.
or
$rootScope.$emit('update', 'Some data');// only lets other $rootScope listeners catch it
Listen Ctrl :-
$rootScope.$on('update', function (event, data) {
console.log(data); // 'Some data'
});
I saw a piece of code in a controller recently that went something like:
.controller('foobar', ['$scope', '$rootScope', function($scope, $rootScope) {
var eventHandler = $rootScope.$on('some-event', function() {
...
});
// remove eventHandler
$scope.$on('$destroy', eventHandler);
}]);
Questions:
Is executing the eventHandler "deregistration" function on $scope's $destroy event necessary?
If yes, would executing the deregistration function on $scope's $destroy event have been necessary if 'some-event' was $on $scope instead of $rootScope?
How do I know when I need to execute a deregistration function? I understand detaching or unbinding events is common for cleanup in JavaScript, but what rules can I follow to know when to do this in Angular?
Any advice about understanding this snippet/"deregistration" would be much appreciated.
In the example above the destroy method is necessary. The listener is bound to the $rootscope which means that even after the controller gets $destroy-ed the listener is still attached to the dom through the $rootscope. Every time the controller is instantiated a new eventhandler will be created so without the destroy method you will have a memory leak.
However if you bind the listener to the controllers $scope it will get destroyed along with the controller as the $scope gets destroyed so the listener has no connection to the dom thus making it eligible for garbage collection
Event handlers are only deregistered on controller's $destroy event when it is on that controller's $scope.
The deregistering would be unnecessary if it's on $scope since that's handled for you by Angular.
Generally if it's not tied to instance of the individual element, controller, or service you are listening on then that is when you need to handle deregistering yourself.
A good example is a directive that registers event listeners on the $document:
var module = angular.module('test', []);
module.directive('onDocumentClick', function directiveFactory($document) {
return {
link: function (scope, element, attrs) {
var onDocumentClick = function () {
console.log('document clicked')
};
$document.on('click', onDocumentClick);
// we need to deregister onDocumentClick because the event listener is on the $document not the directive's element
element.on('$destroy', function () {
$document.off('click', onDocumentClick);
});
}
};
});
I have been working with the excelent ngStorage plugin for angular.
When setting it up you can declare a $scope-node connected to the localstorage like this:
$scope.$store = $localStorage;
$scope.$store is now accessible in all controllers etc.
I want to remove some stuff from localstorage and access it using broadcast instead.
In my init I performed:
$scope.taskarr = [];
$rootScope.$broadcast('taskarrbroad',$scope.taskarr);
What is required in order to add, remove and $watch this array, none of the mentioned seem to work.
Here, nothing happens
controller('textController', function($scope,$routeParams){
$scope.$watch('taskarrbroad.length', function(){
console.log($scope.taskarr.map(function(task){
return task.content;
}).join('\n'));
})
})
Here I can access $scope.taskarr and update it, but the view isn't updated. $scope.$apply() didn't help either (the timeout is because it's already within a digest.
controller('stateSwitchController', function($scope, $routeParams, $timeout){
$scope.taskarr = $scope.$store[$routeParams.state].taskarr || [];
console.log($scope.taskarr);
$timeout(function() {
$scope.$apply();
})
}).
$broadcast is a way to send events to other parts of your application. When you broadcast an event, someone else has to listen to that even with $on(). Something like:
// Some controller
$rootScope.$broadcast('my-event', eventData);
// Some other controller
$scope.$on('my-event', function() {
console.log('my-event fired!')
});
$watch is something else, it's not an event listener per se, it's a way to attach a function that gets called when that value changes, and that value has to be on the scope. So your watch should look like this:
$scope.$watch('taskarr.length', function(){
});
Since you've named the array taskarr on the scope.
I am playing with Angular and SignalR, I have tried to create a service which will act as a manager.
dashboard.factory('notificationsHub', function ($scope) {
var connection;
var proxy;
var initialize = function () {
connection = $.hubConnection();
proxy = connection.createHubProxy('notification');
proxy.on('numberOfIncidents', function (numOfIncident) {
console.log(numOfIncident);
$scope.$emit('numberOfIncidents', numOfIncident);
});
connection.start()
.done(function() {
console.log('Connected');
})
.fail(function() { console.log('Failed to connect Connected'); });
};
return {
initialize: initialize
};
});
however I get the error Error: Unknown provider: $scopeProvider <- $scope <- notificationsHub.
How can I use pubsub to pass all the notifications to the controllers? jQuery maybe?
$scope does not exist in this context as that's something injected when a controller is created and a new child scope is made. However, $rootScope is available at the time you need.
Also, be aware $emit() goes upward and your controller scopes wont see it. You would either need to switch to $broadcast() so the event goes downwards or inject $rootScope as well to the controllers you want to be able to subscribe to 'numberOfIncidents'
Check out the angular docs and a useful wiki on scopes.
Here is a great example showing how to wrap the proxy in a service and use $rootScope for event pub/sub.
http://sravi-kiran.blogspot.com/2013/09/ABetterWayOfUsingAspNetSignalRWithAngularJs.html
As already noted in johlrich's answer, $scope is not avaliable inside proxy.on. However, just switching to $rootScope will most likely not work. The reason for this is because the event handlers regisrered with proxy.on are called by code outside the angular framework, and thus angular will not detect changes to variables. The same applies to $rootScope.$on event handlers that are triggered by events broadcasted from the SignalR event handlers. See https://docs.angularjs.org/error/$rootScope/inprog for some more details.
Thus you want to call $rootScope.$apply() from the SignalR event handler, either explicitly
proxy.on('numberOfIncidents', function (numOfIncident) {
console.log(numOfIncident);
$scope.$apply(function () {
$rootScope.$emit('numberOfIncidents', numOfIncident);
});
});
or possibly implicitly through $timeout
proxy.on('numberOfIncidents', function (numOfIncident) {
console.log(numOfIncident);
$timeout(function () {
$rootScope.$emit('numberOfIncidents', numOfIncident);
}, 0);
});
I tried to use $apply() after changing value, i tried to use $apply(functuin() {value = 3}), and also i tried to use $emit and $broadcast for changing value and it doesn't help.
But i found solution we need in html after in controller you can use
var scope2 = angular.element("#test").scope();
scope2.point.WarmData.push(result);
$scope.$apply();
P.s. I understand that it is very old question, but may by smb, as i, need this solution.