Tried to find some basic information for AngularJS $rootScope.$broadcast, But the AngularJS documentation doesn't help much. In easy words why do we use this?
Also, inside John Papa's Hot Towel template there is a custom function in the common module named $broadcast:
function $broadcast() {
return $rootScope.$broadcast.apply($rootScope, arguments);
}
I did not understand what this is doing. So here are couple of basic questions:
1) What does $rootScope.$broadcast do?
2) What is the difference between $rootScope.$broadcast and $rootScope.$broadcast.apply?
$rootScope basically functions as an event listener and dispatcher.
To answer the question of how it is used, it used in conjunction with rootScope.$on;
$rootScope.$broadcast("hi");
$rootScope.$on("hi", function(){
//do something
});
However, it is a bad practice to use $rootScope as your own app's general event service, since you will quickly end up in a situation where every app depends on $rootScope, and you do not know what components are listening to what events.
The best practice is to create a service for each custom event you want to listen to or broadcast.
.service("hiEventService",function($rootScope) {
this.broadcast = function() {$rootScope.$broadcast("hi")}
this.listen = function(callback) {$rootScope.$on("hi",callback)}
})
What does $rootScope.$broadcast do?
$rootScope.$broadcast is sending an event through the application scope.
Any children scope of that app can catch it using a simple: $scope.$on().
It is especially useful to send events when you want to reach a scope that is not a direct parent (A branch of a parent for example)
!!! One thing to not do however is to use $rootScope.$on from a controller. $rootScope is the application, when your controller is destroyed that event listener will still exist, and when your controller will be created again, it will just pile up more event listeners. (So one broadcast will be caught multiple times). Use $scope.$on() instead, and the listeners will also get destroyed.
What is the difference between $rootScope.$broadcast & $rootScope.$broadcast.apply?
Sometimes you have to use apply(), especially when working with directives and other JS libraries. However since I don't know that code base, I wouldn't be able to tell if that's the case here.
$rootScope.$broadcast is a convenient way to raise a "global" event which all child scopes can listen for. You only need to use $rootScope to broadcast the message, since all the descendant scopes can listen for it.
The root scope broadcasts the event:
$rootScope.$broadcast("myEvent");
Any child Scope can listen for the event:
$scope.$on("myEvent",function () {console.log('my event occurred');} );
Why we use $rootScope.$broadcast? You can use $watch to listen for variable changes and execute functions when the variable state changes. However, in some cases, you simply want to raise an event that other parts of the application can listen for, regardless of any change in scope variable state. This is when $broadcast is helpful.
Passing data !!!
I wonder why no one mention that $broadcast accept a parameter where you can pass an Object
Example:
// the object to transfert
var obj = {
status : 10
}
$rootScope.$broadcast('status_updated', obj);
$scope.$on('status_updated', function(event, obj){
console.log(obj.status); // 10
})
What does $rootScope.$broadcast do?
It broadcasts the message to respective listeners all over the angular app, a very powerful means to transfer messages to scopes at different hierarchical level(be it parent , child or siblings)
Similarly, we have $rootScope.$emit, the only difference is the former is also caught by $scope.$on while the latter is caught by only $rootScope.$on .
refer for examples :- http://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
Related
I need to update one $scope value of controller-2(already loaded) from controller-1. I don't want to use $rootScope.
Can I use broadcast or emit for same?
If the controllers are in a child-parent relationship, you can use:
$emit (if controller1 is child of controller2)
$broadcast (if controller2 is child of controller1)
If they are not in this relationship, then you need to:
emit from controller1
catch the event in the closest parent (could be the app)
broadcast new event from this parent
catch the event in controller2
Of course... this is not always the best way to go.
If you prefer, you can use other ways to communicate (e.g. use a common service).
Use brodcast on rootscope and listen on scope.
The best practice is to create a service for each custom event you want to listen to or broadcast.
.service("hiEventService",function($rootScope,$scope) {
this.broadcast = function() {$rootScope.$broadcast("hi")}
this.listen = function(callback) {$scope.$on("hi",callback)}
})
You can use $localStorage for change that value. or create one service for get and set that value.
According to this answer I just want fire event from controller to another
Call a method of a controller from another controller using 'scope' in AngularJS
$scope.$on("myEvent", function (event, args) {
$scope.rest_id = args.username;
$scope.getMainCategories();
});
And in the second controller you'd just do
$scope.initRestId = function(){
$scope.$broadcast("myEvent", {username: $scope.user.username });
};
But I don't use $scope in my application, just controllerAs and this. There is a way to fire event without inject the scope ? Or should I inject $scope anyway ?But I read in another answer that using both scope and controllerAs is bad practice.
It's not possible to register an $emit or $broadcast event without $scope or $rootScope being injected in the controller.
It is indeed bad practice to use $scope variables and functions since the instance of your controller is already injected inside the $scope with the controllerAs syntax.
But there is not other choice than injecting scope objects if you want to use these events.
However you shouldn't use $emit or $broadcast events just to share data. These events are used for application wide information (like user has logged in or logged out...etc.)
A good practice when using angular events is to prefer $rootScope.$emit because $scope relies on the hierarchy of your components.
For example:
$scope.$emit will emit to the parent component.
$scope.$broadcast will broadcast to children components.
Then $rootScope.$broadcast will broadcast events to the rootScope as well as the scope (which may make your code messy real quick)
$rootScope.$emit is to be preferred as it registers the event application wide and makes it available to the rootScope only. ($rootScope.$on)
Another good practice is to unbind your custom events.
Whenever a component or directive is unloaded/destroyed, the event listener will still reside inside rootScope resulting in possible memory leaks.
To unbind an event:
var unbind = $rootScope.$on('logout', function(event, data) {
console.log('LOGOUT', data);
});
$scope.$on('$destroy', unbind);
I think that generally overuse of $broadcast and $emit is a bad practice too. If you don't want to use the $scope, why not moving the logic handled by events to a service?
I'm trying to write a script to access the rootscope and change a variables value from the dev tools. I can get the scope and the rootscope, but my changes don't show up in my bindings. However if I use a bound input everything is fine.
Example:
<input ng-model="mode"></input>
<span ng-bind-html="mode"></span>
update each other fine but
angular.element("body").scope().$root.mode = 'test'
Updates the controller but has no effect on the bindings. I have compared my original controller and my console controller using === and they are equal.
So from what I read I need to broadcast or emit that I've changed the scope, however I can't seem to figure out what to broadcast , or if I am broadcasting correctly.
Broadcast code attempts:
scope.$apply(function() {root.$emit('change', {mode: 1})})
scope.$apply(function() {root.$emit('stateChangeSuccess', {mode: 1})})
root.$apply(function() {root.$emit('change', {mode: 1})})
root.$apply(function() {root.$emit('stateChangeSuccess', {mode: 1})})
Additionally it should be noted that changing the input will change the span but not my copy of the root scope.
root.mode = 'console'
type 'input text' in the input
root.mode returns 'console'
I don't think this is possible with events (perhaps I'm wrong). There is another way to do make changes from the console though.
The reason angular.element("body").scope().$root.mode = 'test' doesn't update your bindings is that Angular doesn't "know" that a change to the scope has been made. If you are making updates to your data model from "outside" the Angular world, you will need to wrap you changes in an $apply() function:
var $rootScope = angular.element("body").scope().$root;
$rootScope.$apply(function() {
$rootScope.mode = 'hi';
});
FYI, when sending an event from the $rooteScope, you'll want to use $broadcast instead of $emit. $broadcast broadcasts an event "downward" to all descendant scopes, while $emit sends the event "upwards" to ancestor scopes. If you $emit from the $rootScope, there are no ancestor scopes to pick up the event (though perhaps the $rootScope will?).
If I understand you correctly, scope() === $root, otherwise you're making some false assumptions here.
WHat I believe is happening is that you are changing value of root scope but your bindings are actually using another scope due to prototype inheritance(eg the string property is actually created in scope() when you bind against it).
Try either of these two tricks:
1) Either try scope.$apply(function(){scope.mode ='test'});
2) $root.mode={name:'test'}; and bind against mode.name in html
Notice that you should be able to fire a $digest cycle using $apply on root scope and it should propagate down to child scopes too. $root.$apply();
Why I use scope.$parent().$emit() other than scope.$emit() is when the directive use scope:true and there's one more directive use scope:true, and they are placed at same DOM node.
Then scope.$emit() will emit event to the other directive too and can be catch as same as parent scope. But scope.$parent().$emit() will only emit event to parent. Is it good to use scope.$parent().$emit()?
It seems not matter whether other directives can catch the event or not, but I'm not sure about this.So maybe ONLY emit to parent can be good at some case.
Here's a example plunk
"second-directive" event can only be catch by MainCtrl. but "second-directive-two" can be catch by MainCtrl and first directive.
Here is related question: How to stop $broadcast events in AngularJS?
You can use
$rootScope.$broadcast('second-directive-two', 'from second');
instead of
$scope.$parent().$emit('second-directive-two', 'from second');
and cancel event handling in controller
$scope.$on('second-directive-two',function(event,args){
event.preventDefault();
});
And don't handle event 'second-directive-two' in first directive if event has been prevented
$scope.$on('second-directive-two',function(event,args){
if (!event.defaultPrevented) {
// do useful job
}
});
Here is plunk
Update:
Why it isn't good practice ?
In this case you are tied to structure of DOM hierarchy. Imagine that you have to move directive First to partial and include it via ng-include. Scope hierarchy has been changed. $scope.$parent().$emit() doesn't work since this moment. And you spent time to fix this problem.
Generally $scope.$parent().$emit() generate potentially problem code
It sound like you could use scope.$broadcast() instead of emit. It is like emit but the data bubbles the other way around in the scope chain.
I was wondering two things, in the context of angularJS event handling.
How is defined the order in which handlers listening to the same event are triggered?
Is it a sign of a bad design if you start wondering about this?
After reading documentation on angular $on, $broadcast and $emit as well as native DOM event flow I think I understand in which order event handlers will be trigger in different scopes. The problem is when several handlers listen in the same scope ($rootScope for example) from various places (Controllers vs Services for example).
To illustrate the problem I have put together a jsfiddle with one controller and two services, all communicating through $rootScope http://jsfiddle.net/Z84tX/
Thanks
Very good question.
Event handlers are executed in order of initialization.
I haven't really thought about this before, because my handlers never needed to know which one run first, but by the look of you fiddle I can see that the handlers are called in the same order in which they are initialized.
In you fiddle you have a controller controllerA which depends on two services, ServiceA and ServiceB:
myModule
.controller('ControllerA',
[
'$scope',
'$rootScope',
'ServiceA',
'ServiceB',
function($scope, $rootScope, ServiceA, ServiceB) {...}
]
);
Both services and the controller define an event listener.
Now, all dependencies need to be resolved before being injected, which means that both services will be initialized before being injected into the controller. Thus, handlers defined in the services will be called first, because service factories are initialized before controller.
Then, you may also observe that the services are initialized in order they are injected. So ServiceA is initialized before ServiceB because they are injected in that order into the controller. If you changed their order inside the controller signature you'll see that their initilization order is also changed (ServiceB comes before ServiceA).
So, after the services are initialized, the controller gets initialized as well, and with it, the event handler defined within.
So, the end result is, on $broadcast, the handlers will be executed in this order: ServiceA handler, ServiceB handler, ControllerA handler.
It's a bit messy (and I wouldn't recommend it) to go about this route, but wanted to provide an alternative incase you are unable to ensure serviceB will be initialized before serviceA and you absolutely need serviceB's listener to be executed first.
You can manipulate the $rootScope.$$listeners array to put serviceB's listener first.
Something like this would work when adding the listener to $rootScope on serviceB:
var listener, listenersArray;
$rootScope.$on('$stateChangeStart', someFunction);
listenersArray = $rootScope.$$listeners.$stateChangeStart;
listener = listenersArray[listenersArray.length - 1];
listenersArray.splice(listenersArray.length - 1, 1);
listenersArray.unshift(listener);
To provide an answer to #2 (because I think #Stewie's answer to #1 is a really good one), while I'd hesitate to ever offer conclusive rules that say, "if you see this, then it's bad code", I would offer to say that if you have two event handlers, and one can only execute after the other has run: you should evaluate why that is the case and if you couldn't better encapsulate or organize the way your logic executes.
One of the primary use cases of pub/sub event broadcasting/listening is to allow separate components that are fully independent of one another to operate on their domain of influence in an independent way asynchronously. By one handler having to operate only after another handler has run first you are removing the asynchronous nature of pub/sub by adding a secondary requirement (though possibly necessary).
If it's an absolutely necessary dependency, then no: it's not a symptom of bad design - its a symptom of the requirements of that feature.
I'm a new user to Angular JS so please forgive if the answer is less than optimal. :P
If you require the order of the functions triggered by an event to be dependent (i.e., Function A then Function B) then might not creating a trigger function be better?
function trigger(event,data) {
FunctionA(event,data);
FunctionB(event,data);
}
$rootScope.on('eventTrigger',trigger);
To add another comment on point #2, if you need to guarantee order you could implement an observer pattern using a service with an array of listeners. In the service you can define "addListener" functions that also defines how the listeners are ordered. You can then inject this service into whatever other components need to fire events.
It is a bit hacky and absolutely not recommended to use in your design, but sometimes you don't have a choice (been there so many times).
$rootScope.$on('someEvent', () => {
setTimeout(eventHandler1, 1);
});
$rootScope.$on('someEvent', eventHandler2);
const eventHandler1 = () => {
console.log('This one runs last');
};
const eventHandler2 = () => {
console.log('This one runs first');
};
As you can see from the example, I have used setTimeout to trick the order of running the actual handler and make the eventHandler1 handler to run last, although it has been called first.
To set the execution priority, just change the setTimeout delay as necessary.
This is not ideal and is only suited for specific cases.