I am using angular-js. I have a service that need to trigger event each time something occurs. For doing it I need an object that will act as event aggregator.
Do I need to build one? Or should I use the $rootScope?
In case I should use $rootScope, how can I ensure that there is no event names conflicts?
Is it efficient to use $rootScope for events that don't need them to propagate to child scopes?
I modeled and implemented the following mechanism in a web project for tablets:
Define notifications in your services. I did not want to use the term events since I did not want it to be confused with DOM events by other developers in my team. And a semi-typed name for a notification is easier for IDE with intellisense support and for debugging. For example, I have one Device service that would $broadcast(Device.Notification.OrientationDidChange) when a tablet device's orientation is changed.
Use Scope objects to $broadcast or $emit notifications, depending on your need. For example,
For global notifications like the previous one, I do: $rootScope.$broadcast(Device.Notification.OrientationDidChange). So all listeners can listen on their own scope without injecting $rootScope.
For local notifications that could possibly only affect the current scope (and its children), such as a notification that a scope needs to tell all its child scopes that the layout of the current controller is changed, usually I do: scope.$broadcast(UI.Notification.NeedsLayout), where UI is a predefined service to hold UI related constants, and scope is the current scope.
For certain notifications that a child scope needs to send upwards, for example, a slider directive that tells ascendant scopes that the rangeStart value has changed (in addition to regular two way binding), I use: scope.$emit(Slider.Notification.RangeStartDidChange), where scope is the current scope.
This approach is a bit verbose in a small project. You might want to stick to using $rootScope.$emit(Notification) all the time, and let all listeners to do $rootScope.$on(Notification, callback) to receive these notifications.
In some situations, you might want to define these notifications in a centralized service in order to more easily avoid name conflicts. It really based upon your project's naming convention.
The implementation (actual values) of these notifications may vary. I prefer using strings.
With $broadcast or $emit you can also actually pass additional arguments to the listeners, for example, $broadcast(Notification, arg1, arg2)... Angular's documentation is quite detailed.
Take a look at http://docs.angularjs.org/api/ng.$rootScope.Scope#$broadcast.
Using $rootScope as event aggregator is perfectly fine unless you are triggering events outslide digest cycle or triggering multiple (100+) events at the same time where some other solutions may be more appropriate .
One accepted practice would be to use namespacing (namespace:event - pattern used by Backbone.Marionette)
Use $emit on a child scope instead of $broadcast on $rootScope - $emit propagates only upwards, where $broadcast propagates downwards - to all children
Related
I have a hot RxJS Observable that I want to respond to in different ways depending on the context of the application. The Subject emits a new event based on some global action intercepted by a directive, but then I want
If a child component is subscribing to the Subject, then the child should handle the event
Otherwise, use a global handler
I can get the number of subscribers from the Subject and then tell the global handler to ignore if there are multiple subscribers, but it's not part of the API, so it seems like it may not be the right way to handle it. So what is the right way to do this?
Also, should the global event handler be part of the directive, the service, or should that be in a new component?
You can put global event subject in a global app.service and inject it in other component for subscription.
Although the ideal component should just have its own service maybe to handle complex event, but sometimes I feel directly inject a global service keeps the code cleaner. Otherwise, if you really want complete isolation or the component should be widely reusable e.g UI dropdown list, I suggest to use #Output to fire events (btw angular EventEmitter inherit Subject) and #Input to take in variables.
I have a problem I haven't come across before, and I'm completely lost as to how to solve it.
I have a container which is used to display success and error messages across a wide variety of views. This container is defined by an Angular directive.
The problem is that I would like to have a service or something similar which I can inject into my controllers. I could then use methods on this service to make the "alert box" appear and/or change in content.
How do I go about achieving this or a similarly DRY setup in Angular?
Three perfectly good methods:
Use angular's event system, rootScope.$broadcast or scope.$emit and scope.$on as listener, see the documentation here: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$broadcast
https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$emit
https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$on
inject the service into the "alert box" directive, let it register itself somehow with the service, that way the service knows of its existence and can control the directive's controller, or its scope, or the element directly, or whatever logic you wish to use.
Use a container as parent, that has the orchestrating logic. the child directive's can require the container controller, and call functions etc. on the container, one child directive is then able to trigger another child through this orchestrating parent container.
EDIT:
My point wasn't to show you how to do it in the most generic and cleanest way, because I don't know your exact situation, you should always consider how generic and abstracted you want your functionality.
Here is a very very simple example to showcase that it's not about finding some complex system or pattern, but finding something that works well for you
method 2 example:
angular.module('myApp').service( 'myService', myService );
function myService(){
this.alertBoxes = {};
this.registerAlertBox = function(name, handle){
this.alertBoxes[ name ] = handle;
}
this.toggleAlertBox = function(byName){
this.alertBoxes[ byName ]();
}
}
I personally would use method3 from the looks of what kind of functionality you're looking for, method 2 could be a generic pubsub service using rootScope.$broadcast and scope.$on, or not even bothering with those and doing it like a very straight forward pubsub.
method 1 really is the simplest way, but obviously would benefit from being abstracted away into a service, where you inject the service everytime you need a very specific pubsub event.
For an example of a pub/sub pattern: Pub/Sub design pattern angularjs service
I am new to Javascript and AngularJS. Both seem to me as an event listener. What are the differences? Can I use them interchangeably? How?
$scope.$on will catch events that are.$broadcast() on the $scope whereas addEventListener listens to any events on the page. They're similar but not interchangeable.
If working in an angular app, I would definitely use $scope.$on unless otherwise needed, i.e. catching events from outside of the angular app. You will gain testability if you're writing unit tests and will only be looking for events from your own code, which is probably what you're looking for.
No. They operate on different objects, and are not interchangeable, although they are very similar in what they do. Both add an event listener to an object which can emit events, but scopes are not HTMLElements (like document), and different events are fired on scopes than on HTMLElements.
$scope.$on should be used wherever possible. Usually if there is an Angular way to do something, you should do it that way.
$scope.$on is used in conjunction with $scope.$emit which sends data to all parent controllers, and $scope.$broadcast which sends data to all child controllers. This gives you greater control over the flow of data through the app. Furthermore, handling events inside of a native event listener will break 2-way data binding.
I am building an app in angular, which consumes different APIs and Gives options for the user to select it will be recorded and sent back to the server.
I have designed it as follows.
All the common logic in Main Controller and all other options in different controllers as the child of main controller.
Main Controller retrieve all the data that are required to run the app.
which is consumed by all other child controllers.
To make sure data is loaded I am using promise attached to scope. So all the child controller will know data loaded.
I have moved data updation part of all child controllers to main controller
because all the updates happen in one object.
Child Controller emit/broadcast to communicate between child and main. So when update happens child will emit an event with data which will be captured by Main and it will do the update.
MainController {
$scope.loaded = DataService.get();
$scope.userOptions = {};
$scope.$on('update',function(){
updateUserOptions();
})
}
ChildController {
$scope.loaded.then(function(){
//all logic of child controller
}
$scope.onselect = function(){
$scope.$emit('update',data);
}
}
Questions
Is it a good practice to use events between controllers ?
is it good to use promise attached to scope for child controllers ?
Will it improve my code if I start using services ?
I will try to answer your question based on my own experience. Recently I've built a single page application and I've refactored its architecture.
Here are my answers:
Is it a good practice to use events between controllers? IMHO, it is the most flexible way to share information between all controllers even if they have isolated scope (using $broadcast or $emit for example). This is called the Observer design pattern. However, you can use services instead of events to share data between them. If you are going to use $rootScope, be careful as all the $scopes inherit from $rootScope.
is it good to use promise attached to scope for child controllers ? Firstly, you have to learn about how scope inheritance works. You have to take care to avoid property shadow in JS. Secondly, I would move out all the logic from scope.loaded in ChildController to a service such as ChildService. Keeping the business logic (such as request, etc) in Services instead of Controllers, will ensure it can be re-used.
Segregation of business logic is good design principle.
Will it improve my code if I start using services ? I answered this question above.
In addition, in order to build a good architecture I've read this angular style guide written by John Papa.
I recommend the following changes:
To make sure data is loaded I am using promise attached to scope. So all the child controller will know data loaded.. Instead I would emit a custom 'loaded' event in the MainController using $scope.$emit('loaded'). After that, in the ChildController I would use $scope.$on('loaded', function(){}) to handle the event.
I would move the updateUserOptions function to a service and inject the it into just the controllers that need it.
I hope that helps!
Is it a good practice to use events between controllers ? Not as the main form of data sharing, but you can use it to notify about system events, such as data ready.
Is it good to use promise attached to scope for child controllers ? Don't use scope inheritance, it causes lots of annoying problems.
Will it improve my code if I start using services ? Yep.
This is what I would do in your place:
dataService - this service is responsible for all data coming in / going out. Whenever a request for data is made (no matter which controller asked for the data), the service caches the data (save the promise is good enough). All further requests get the cached data unless they specify they want a fresh data. Whenever data is updated (1st time or refresh), the service broadcasts a 'dataReady' event via $rootScope to which the main controller and other subscribers can listen.
The service is also responsible for data updates, and when the data is updated you can also broadcast an event via the $rootScope.
When the event is activated, all subscribers query the service, and get the data they need.
Controllers - avoid controllers, use directives with isolated scope, and pass the data between them using attributes. In this way you can be sure that each directive gets what it needs, and not everything. The directives can communicate using attributes, services, broadcast / emit or require their parents / siblings if they work closely together.
Is it a good practice to use events between controllers ?
No it's not, it will be deprecated by Angular JS 2.0. It also often leads to unmanagable tangle of events which are hard to understand and debug. Use services to share data between controllers. (Inject same service into multiple controllers, service then holds data, controllers bind to that data and are automatically synchronized) I wrote a blog post explaining this use case.
Is it good to use promise attached to scope for child controllers ?
No it's not. Use promises and resolve data in services. Don't use $scope at all but use controllerAs syntax instead. $scope was deprecated also in Angular JS 1.X because it's usage leads to many different problems with scope inheritance.
Will it improve my code if I start using services ?
YES! Use services for all logic and data manipulation. Use controllers only for UI interaction and delegate everything to services. Also use ui-router for managing state of your application.
I'm not going to answer your questions directly as I have some other comments as well. I think the approach you mentioned is not the best way to build angular applications.
All the common logic in Main Controller and all other options in different controllers as the child of main controller.
It's against all angular style guides to place common logic in controllers. Controllers should only be used for the logic related to the view (data binding, validation, ...). Because the code inside a controller is not reusable, the less code you have in a controller the better. The more logic you have in services, the more scalable your application becomes.
Fix: I suggest you create a service that retrieves data from the server, and inject this service in controllers as you need. Notice also this way offers better dependency management as you can keep track of which controllers need which services exactly.
Nested controllers should be avoided when possible, because angular keeps track of all the active scopes and re-evaluates them in every $apply() loop.
Fix: same as #1, use services instead of the main controller.
To make sure data is loaded I am using promise attached to scope. So all the child controller will know data loaded.
Using a promise for data retrieval is a good practice. But, again, keeping it in a service is much cleaner than main controller.
I have moved data updation part of all child controllers to main controller because all the updates happen in one object.
Child Controller emit/broadcast to communicate between child and main. So when update happens child will emit an event with data which will be captured by Main and it will do the update.
Fix: use a service with an update function instead of events. Events are harder to debug and track. And you need to unregister event handlers on destroying a controller. If you can use a function/promise instead of events, then it's usually a better idea.
Is it a good practice to use events between controllers ?
A problem with your current set-up is that you're implicitly relying on the hierarchy of your controllers (the fact that one is the child of the other) - because you emit the event, only scopes higher up on the hierarchy can catch it. Besides being an implicit connection (that a developer has to remember), this also limits he extendability of this feature.
On the other hand, if you injected a shared service into all the controllers that need it, the connection between the controllers would become explicit and documented, and their scopes' position in the hierarchy independent. This will make your architecture easier to maintain, with the added benefit of also being easier to test, for one.
You can still implement an observer pattern with a service.
is it good to use promise attached to scope for child controllers ?
The issue of polluting scopes pointed out in other answers is valid. This is one of the reasons why it's better to limit the number of objects you attach to your scope, and to use objects as bundles of variables on your scope instead of attaching all the variables to the scope directly.
(For an explanation of these reasons, see discussions about "always having a . in your bindings".)
(Of course, don't do this blindly just to reduce the number of variables, try to find semantic connections between variables that might be bundled together sensefully.)
Will it improve my code if I start using services ?
I think the above answers already outline the answer for this: yes. There are other benefits too, but this format is not best for too long answers, so I won't list anything else now.
All in all, these above pointers are not big issues with your code currently, but if you're looking for the best architecture, I think you can do better.
Answers:
No, it will be deprecated soon.
$scope is deprecated already.
Services is a great choice. Services allow us to share data and behaviour across other objects like controllers.
What is the difference between implementing a $rootScope function and a service? Security wise or performance wise.
I have read this, so I am wondering.
I have been trying to figure out whether or not a certain global function for my app would be best implemented in a service or on the $rootScope itself. To pitch you guys with an idea of what I am making, I'm currently developing a dirty form function in which it prompts the user if he/she navigates away from a certain form. In this case, I decided to best implement it as a global function, so any hints?
Thanks for the responses,
Jan
In this case I would go for a service to avoid having a global state. all new scopes are created from $rootScope. New controllers or whoever uses a scope will have values of $rootscope available. For instance, if you define $rootScope.validate() and in a controller you define a function $scope.validate() because you forget about the first definition, something will certainly go wrong.
There is an article by Misko H. about this http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/
Services are instantianted on demand, whereas $rootScope is created during bootstrap, and can be injected wherever you need them. This is good for testability.
Angular won't instantiate services unless they are requested directly or indirectly by the application.
(http://docs.angularjs.org/guide/dev_guide.services.creating_services)
As #egamonal mentioned Services are more robust way to share common functionality. Not only services are instantiated on demand, they are singleton by nature so once a service gets created, the same instance gets passed around by AngularJS whenever requested. So if something can go on root scope, it can also be implemented using a service.
One think to keep in mind using such global approach is the possibility on memory leak. Instance of JS classes or maybe DOM elements remain in the memory because you have referenced them from you global functions (either in $rootscope or service).