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
Related
If I want to call a function in a controller when I make changes in a different controller, I usually use $on and $broadcast.
However, I've started using $watch more recently. Now, I have a variable in a service, for example this.shouldIReload = false. Whenever I make a certain change(for example, deleting an item in a modal), I change the value in the service, and using $watch, I can call a reload function in another controller. This works.
Which way is better? What are the pros and cons of each?
AngularJS allows you to implement two-way data binding. However, the interesting part is how it detects model changes? The model is usually a plain object like the code below. We can change the name property of $scope.user but how does AngularJS detect the model changed? Does AngularJS enum all the properties of the $scope object?
angular.module('myApp', [])
.controller('BusinessCardController', function($scope){
$scope.user = {
name: 'Tanay Pant'
}
});
<input type="text" ng-model="user.name" placeholder="Full Name" />
There is a digest cycle, where the scope examines all of the $watch expressions and compares them with the previous value. It looks at the object models for changes, if the old value isn't the same as the new value, AngularJS will update the appropriate places, a.k.a dirty checking.
In order for the digest cycle to be execute $apply(fn) has to be run, this is how you enter the Angular world from JavaScript. How does $apply(fn) get called (taken from AngularJs integration with browser):
The browser's event-loop waits for an event to arrive. An event is a user interaction, timer event, or network event (response from a server).
The event's callback gets executed. This enters the JavaScript context. The callback can modify the DOM structure.
Once the callback executes, the browser leaves the JavaScript context and re-renders the view based on DOM changes.
Data Binding
Digest Cycle Explanation
In order to achieve two-way binding, directives register watchers. For a page to be fast and efficient we need to try and reduce all these watchers that we create. So you should be careful when using two-way binding - i.e. only use it when you really needed. Otherwise use one-way:
<h1> {{ ::vm.title }} </h1>
Here it is quite obvious that the title of the page probably won't be changed while the user is on the page - or needs to see the new one if it is changed. So we can use :: to register a one-way binding during the template linking phase.
The main issues I've seen with explosions of watchers are grids with hundreds of rows. If these rows have quite a few columns and in each cell there is two-way data binding, then you're in for a treat. You can sit back and wait like in modem times for the page to load!
Two-way binding is limited almost exclusively to elements that use ng-model. The direction going from view to model uses standard event handlers to detect changes that must be updated within the model (e.g., onchange). The direction going from the model back to the view is updated during a $digest. But we do not call $digest directly.
Every element that is on your page that is going to respond to the digest cycle will, somewhere, attach a listener and an expression to its scope using $watch. When you write {{ foo() }}, or when you use ng-model='user.name', internally there is a call to $watch made on your behalf with a Javascript expression that will be run every time a digest cycle is run. This registration might happen during the compile of the template (our first example), or it might happen during the link phase of a directive (our second).
There is no magic here. The listeners that are attached are regular functions -- in our example, the listener for the expression foo() is provided for you, and it will update the html text on the page, while the listener for the expression user.name will call setText, or setOption, or whatever is required by the particular input which ng-model has been attached.
While angular can handle most of the listening, you can attach your own watch expressions with your own listeners manually inside any function that has access to a scope (scope is important because we will tear down those watchers if the corresponding parts of the page are removed). Be mindful of excess. Bindings aren't free, and the more things that are bound, the slower the page will respond. One-time bindings are one way of reducing this cost. Using $on with $emit and $broadcast are another.
So when is digest called? It is certainly not automatic. If the digest cycle is running, it means someone somewhere called $apply on their scope or on the root scope. ng-model attaches handlers which will respond to regular html events and will make calls to $apply on your behalf. But foo(), on the other hand, will never get called until some other bit of script somewhere calls $apply. Fortunately, most functions that you fill out for angular wrap those functions with a call to $apply, so you don't often have to make the call yourself (e.g., $timeout is wrapped with $apply, which is why we use it instead of setTimeout). But if you were using something outside of the scope of angular (a 3rd party library that connects to events), you would need to remember to call $apply yourself, and just like above, you can do this manually by calling $apply anywhere you have access to a scope.
In order to make Data Binding possible, AngularJS uses $watch API's to observer the changes on the scope. AngularJS registered watchers for each variable on the scope to observe the value in it. If the value of the variable on the scope gets changes, then the view gets updated automatically.
It happens because of the $digest cycle is triggered. Hence, AngularJS processes all the registered watchers on the current scope and the children and check for the updates and call the dedicated watcher listeners until the model is stabilized and no more listeners are fired. Once the $digest loop finishes the execution, the browser re-renders the DOM and reflects the changes
By default, every variable on a scope is observed by the angular. In this way, unnecessary variable are also observed by the angular that is time consuming and as a result page is becoming slow.
Now that $broadcast is almost 7x faster than $emit, there are reasons to use $emit instead of only to use $rootScope.$broadcast?
Why is not good to use $rootScope in all your controllers now that $scope use is transparent, except to handle events?
Am I totally wrong with that concept?
There are a couple things wrong with this situation. Using $rootScope as an event bus can work, but it's not a simple black and white performance decision.
Changes to $broadcast in Angular 1.3.0-beta19 allow for $broadcast to exit early if nobody is listening to the given event. This doesn't automatically mean that $broadcast is more efficient than $emit, only that it doesn't run with no listeners, while $emit still will. This makes it appear to be faster, in the benchmark you reference in your comments.
$broadcast dispatches events downward from the $scope to all child scopes (and their children). With even one listener, $broadcast from $rootScope is going to hit every scope in your application.
$emit, on the other hand, bubbles events upwards. Since $rootScope is the topmost scope, $emit on $rootScope will always touch exactly one scope. If you MUST use $rootScope, $emit will be better.
Using $rootScope has it's own problems, however; If you register a listener from a controller, you will need to manually remove the listener when your controller is destroyed. Controllers are not singletons, and multiple controllers which create a listener and then are destroyed without removing the listener will eventually create a memory leak in your app.
Using a global event bus can also be a problem, as it's relatively easy for multiple components to try to register the same event, or trigger an event that they aren't responsible for, creating subtle bugs that can be very difficult to find.
In general, it's best to use $broadcast from a scope to it's descendants as close to the bottom of the tree as possible. Only services should use $rootScope, as they do not have access to other scopes. In that case, $emit is better, as it reduces the number of scopes touched to 1. However, Almost all cases of cross controller communication can be better managed with correct scope hierarchies that rely on bindings rather than events.
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/
Basically, I am unable to update my controller information when I listen for the $on event if I loaded my html dynamically using ng-include. Plunker example.
If you click once, you'll see the view keeps the original $scope.name. If you click again it will update.
I put a setTimeout on the broadcast to make sure the ng-include was loaded. You can set that to as long as you want, and will never be able to update the $scope on the first try (at least in my example).
Thoughts?
EDIT:
I'm using <ng-include="template"></ng-template>
As an area I can load alternate content in. If there is a better way to do this, please let me know.
setTimeout() is a function out of the control of AngularJS, so AngularJS will not automatically run a digest after the callback runs. That means, your $rootScope.$broadcast() was run, but AngularJS didn't realize that. The next time when you use $rootScope.template = '....';, a digest runs, and the view was updated to the previous run's model.
To solve the problem, you will need to manually call $scope.$apply() at the end of your setTimeout() callback, or use the Angular-wrapped version of setTimeout(), which is $timeout(), that will automatically run a digest afterwards.
Please refer to the docs for more details about digest/apply:
It works for me if you use $timeout instead of setTimeout. Which you should be using for angular applications.
$timeout(function(){
$rootScope.$broadcast('BROADCAST', param);
}, 1000);
There is definitely something wrong with your design if you are trying to do something like this though. Perhaps someone could suggest an alternate solution if you better explained what you are trying to achieve. As you cannot possibly know how long the timeout should be.
A few things:
1) First, do not define the $scope.template in the broadcast function. The ngInclude will not have a file to display until that value is set; so from it makes sense--in my mind--that the template would not be able to make changes before it and the controller are loaded.
2) You never actually apply Controller C2 to the ngInclude. You can do that like:
<ng-include src="template" ng-controller="c2"></ng-include>
Once I do these two things, the code works and updates the first time without the use of the setTimeout() at all.
Plunker