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.
Related
We have a large Angularjs 1.6 application that has $rootscope scattered throughout the app in over 200 places in filters, services, routes, etc.. so it needs to be refactored, but I'm not sure how to know when to remove it. When is it a best practice to use $rootscope in the application?
I've read everything from never, to using it for storing variables, which I assumed was for sharing data between controllers. I've since read that it's better to use factories/services for this use case instead and I also read that one valid use case is to use $rootscope as a global event bus.
I didn't really see this explained in the Angularjs docs.
From ng-book:
When Angular starts to run and generate the view, it will create a binding from the root ng-app
element to the $rootScope. This $rootScope is the eventual parent of all $scope objects.
The $rootScope object is the closest object we have to the global context in an
Angular app. It’s a bad idea to attach too much logic to this global context, in the
same way that it’s not a good idea to dirty the JavaScript global scope.
You are right, you should definitely use Services to share data and logic between your modules.
Putting a lot of logic in your $rootScope means having bad maintainability and modularity in your application, it is also very difficult to test issues.
I highly suggest you to take a look at:
Services AngularJS Documentation
Thinkster brilliant article on how to share data between controllers
Screencast by Simpulton
#Breck421 answer to this question
I know it may be easy to attach everything to $rootScope, but It is just difficult to work on it, make little changes, reusing your code for other applications or modules and test your application in general.
EDIT
Recently I had to fetch some items from API and catch these items in order to show them in a certain view. The item fetching mechanism was in a certain Factory, while the mechanism to format and show the items was in a Controller.
So, I had to emit an event in the Factory when items got fetched and catch this event in the Controller.
$rootScope way
//Factory
$rootScope.$broadcast('refreshItems', items);
//Controller
$scope.$on('refreshItems', doSomething());
It clearly worked but I didn't really like to use $rootScope and I've also noticed that the performance of that task were pretty miserable.
Then I tried giving a shot to Postal.js:
Postal.js is an in-memory message bus - very loosely inspired by AMQP -
written in JavaScript. Postal.js runs in the browser, or on the server
using node.js. It takes the familiar "eventing-style" paradigm (of
which most JavaScript developers are familiar) and extends it by
providing "broker" and subscriber implementations which are more
sophisticated than what you typically find in simple event
emitting/aggregation.
I tried using Postal.js for this kind of needs and I found out that it is really faster than using $rootScope for this purpose.
//Factory
$scope.$bus.publish({
channel : 'reloadItems',
topic : 'reloadItems'
data : items
);
//Controller
$scope.$bus.subscribe({
channel : 'reloadItems',
topic : 'reloadItems',
callback : function () {
resetAndLoadItems();
}
});
I hope I've been helpful.
From Angluar docs: Every application has a single root scope. All other scopes are descendant scopes of the root scope. Scopes provide separation between the model and the view, via a mechanism for watching the model for changes.
Of course this is going to come down to a matter of opinion and style. I tend to follow a style very close to John Papa's Angular Style Guide.
In keeping with the two, and following a good separation of concerns strategy my architecture contains factory models that are shared across the application. My controllers in turn are all bound to the services that hold the shared data.
Using $rootScope as the global event bus is exactly how Angular uses it. Should you tag along and do the same? I don't see why not. But if you are, make sure that the purpose is clearly defined and maybe even use your own service to register events to the global event bus. That way you are decoupling your app from Angular, and if you ever decide that you want to change the framework in which your global event bus lives then you can change it in one place.
This is what I'm suggesting:
Global event bus
// Angular specific: add service to module
angular.module('app').factory('globalEventBus', GlobalEventBus);
// Angular specific: inject dependencies
GlobalEventBus.$inject(['$rootScope']);
// Non framework specific.
// param: fameworkEventBus will be $rootScope once injected
function GlobalEventBus(fameworkEventBus) {
var globalEventBus = this;
globalEventBus.registerEvent(params...){
fameworkEventBus.
}
return globalEventBus;
}
Global data models
My data models are smart and tend to contain functions that provide information about themselves or retrieve/return specific data.
// Angular specific: add service to module
angular.module('app').factory('dataModel', DataModel);
function DataModel() {
var dataModel= this;
dataModel.myData = {};
dataModel.GetSpecificData = funtion(param){
return ...
}
return dataModel;
}
The controller
// Angular specific
angular.module('app').controller('MyController', MyController);
// Angular specific: inject dependencies to controller
MyController.$inject = ['dataModel'];
// By convention I use the same parameter name as the service.
// It helps me see quickly if my order of injection is correct
function MyController(dataModel) {
var myController = this;
// Bind to the service itself, and NOT to the service data property
myController.myData = dataModel;
myController.doStuff = function(){
}
}
Here is a fun post about binding to services and not to service properties.
All in all you have to be the judge of what works best for you. A good system architecture and good style have saved me countless hours of solving completely avoidable problems.
After doing some more work with Angular and more reading I found this basic rule of thumb for using $rootscope that I wanted to add to the other answers:
Only add properties that are static or constant. Anything else that
represents a changing state or a mutable value should have a
corresponding directive or controller to handle it.
In other words, is it true that I have to wait for the next event cycle to broadcast using $rootScope.broadcast(), so that other controllers using $rootScope.on() can pick up the broadcasted event?
It is not the usual app flow, but let's say if we have a demo mode in the app which would short circuit some logic, and not need to go the server to fetch something, but can provide the demo data using the broadcast(), is it true that we have to use
setTimeout(function() {
$rootScope.broadcast(...);
}, 0);
so that other controllers will pick it up?
The reason is, other controllers might not exist yet. If the app module defines 5 controllers, or use dependency injection to declare it depends on 2 sets of controllers, abc.xyz.controllers and abc.efg.controllers.
Since a controller definition is
.controller("name", function(...) {
})
so by providing the function, you don't know when the function is called (to create the controller), but assuming all creations were done in the first event cycle, then if you wait one cycle to broadcast, then you can be sure that all controllers will pick up the broadcast? Is this formally documented too?
About shared state between controllers. I have a hard time finding the right way to do this from all the possible solutions recommended on SO. I made this sketch to illustrate the basic idea I had about this so far using a factory.
There is the factory myFactory, that holds a shared variable sharedVar.
The controllers Ctrl1, Ctrl2, Ctrl3 want to access always the updated version. They also can call an updateViaHttp.
Is that the right purpose of a factory? (in general to share state,
specific to the other options like service and provider)
If so, how to watch changes of the sharedVar in a proper way? (by
reference of objects, $watch, events (broadcast, on), ...)
Is there a general pattern that works well for objects, arrays and
primitives.
You've got the basic idea right, assuming by 'factory' you mean 'service' -- it's kind of confusing, I know, because services are declared using factory functions. That said, it's an important distinction to make so that you'll have an easier time finding documentation, etc.
Watch changes either just by using object references and being careful about watch depths in Angular (my preferred method) or by explicitly registering $watch statements (still be careful about watch depths). Generally I'm of the opinion that you shouldn't overuse broadcasts as it can make your code a little messy. It also kind of defeats the point of the service in this case, which is to be the source of shared state.
My general pattern for creating services is to bind everything I want to use to an object (both data and functions) and then return that object in the factory function. Sometimes you have to introduce some extra nesting so that the Javascript prototypical inheritance doesn't mess with you (see the watch depths thing again) but that's the general idea.
An example service for your set up:
angular.factory('shareAndUpdate', ['dependencyInjection', function(dependency) {
var srvc = {};
srvc.sharedVar = 'something';
srvc.updateViaHttp = function(){ something };
return srvc;
}]);
factories vs services vs providers - only differences are related to how the Dependency Injector provides instances of them to you. Services are specifically designed for providing singletons, but are just a wrapper over factories that add the singleton specific functionality. Nothing stopping you from returning a singleton from a factory.
using services to share state - you need a singleton and a service makes defining and injecting singletons easy.
SomeService:
var foo = {
bar = 'a';
};
function getFoo() {
return foo;
}
SomeController[SomeService injected here] :
$scope.foo = SomeService.getFoo();
$scope.$watch('foo.bar', ...);
$scope.setFooBar = function(val) {
$scope.foo.bar = val;
};
2
The general pattern here is to never do a
$scope.foo = { bar: 'Some other reference' }; because then all your other things depending on SomeService will not get the new reference when you overwrite it - the "infamous" always use a "dot" in $scope stuff issue.
You are probably looking for something like a pubsub service:
https://www.npmjs.com/package/angular-pubsub
However, in my experience, through proper design, you can minimize the necessity to share data in between non-nested controllers.
Sometimes it is unavoidable, though, for stuff like login credentials, permissions, stuff that is all-app-encompassing. In such occasions you can use a service to indeed share/get the state in between controllers, or you can go for the fully-fledged pubsub mechanism.
A factory is just another way of specifying a service. A factory, when called, gives an instance of a service. This service you can use for everything that you want, one of those things being sharing state in between your controllers.
You can watch a shared variable in many ways, the easiest being inheriting scopes, but, as you mentioned, sometimes your controllers don't necessarily inherit their scopes. Then you can use a pubsub service or just broadcast events on a shared scope for both controllers (like $rootScope, which is the parent of all controllers' scopes for your app).
If you were to use an existing pubsubservice, it would still be up to your implementing controllers to actually do the subscribe and watching on a specific variable and updating their corresponding scopes accordingly. However, that can be avoided if you design your app in such a way that your controllers inherit the variable from a shared scope. Then they will automatically update their stuff using the normal angular mechanism. That, sadly, can not always be achieved and then what you are left with is having to implement a pubsub service.
I'm making a fairly trivial notification system where I want a main menu (outside ng-view) to display the last activity of the user, from what I've read it looks like a service is best used to communicate between controllers. How should I detect when something happens in controller A immediately from controller B. Should I $watch the variable of the service? Or is there an easier way to do this? Or given should I just have a root scope variable and $watch and change that with the controllers?
You should use $broadcast and $on methods. Here's a good example found on the internet
Also, there is no more an issue with $broadcast over $emit in recent versions of angular, because $broadcast runs as fast as $emit.
Don't use $watch for this because it will create a lot of events ( angular has also a limit of 10 rerun iterations to prevent deadlock ).
The watch listener may change the model, which may trigger other listeners to fire.
This is achieved by rerunning the watchers until no changes are
detected. The rerun iteration limit is 10 to prevent an infinite loop
deadlock.
Read more here
$watch will trigger everytime your variable change. Your can use $emit or $broadcast with $on for a less regular event.
This can be done in a factory : how to emit events from a factory
Could store something in the global $window and create a watch expression on it, that is just off the top off my head, might be a better way to do it.
Also take a look at this as it is like the sort of thing you need to do:
Share data between AngularJS controllers
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/