What is .$on() in AngularJS - angularjs

I got this code $rootScope.$on('abc',function(event, next, current){ }); in a tutorial.
My question is what is .$on()? If it is a function, then why is it preceded by $?

$on is related to $broadcast and $emit - which is a way to trigger code from other places.
The first thing about $on you should know is that it's a method of $scope
The second thing you should know is $ prefix refers to an Angular Method, $$ prefixes refers to angular methods that you should avoid using.
Now lets get into detail about what $on is.
Below is an example template and its controllers, we'll explore how $broadcast/$on can help us achieve what we want.
<div ng-controller="FirstCtrl">
<input ng-model="name"/>
<button ng-click="register()">Register </button>
</div>
<div ng-controller="SecondCtrl">
Registered Name: <input ng-model="name"/>
</div>
The controllers are
app.controller('FirstCtrl', function($scope){
$scope.register = function(){
}
});
app.controller('SecondCtrl', function($scope){
});
My question to you is how do you pass the name to the second controller when a user clicks register? You may come up with multiple solutions but the one we're going to use is using $broadcast and $on.
$broadcast vs $emit
Which should we use? $broadcast will channel down to all the children dom elements and $emit will channel the opposite direction to all the ancestor dom elements.
The best way to avoid deciding between $emit or $broadcast is to channel from the $rootScope and use $broadcast to all its children. Which makes our case much easier since our dom elements are siblings.
Adding $rootScope and lets $broadcast
app.controller('FirstCtrl', function($rootScope, $scope){
$scope.register = function(){
$rootScope.$broadcast('BOOM!', $scope.name)
}
});
Note we added $rootScope and now we're using $broadcast(broadcastName, arguments). For broadcastName, we want to give it a unique name so we can catch that name in our secondCtrl. I've chosen BOOM! just for fun. The second arguments 'arguments' allows us to pass values to the listeners.
Receiving our broadcast
In our second controller, we need to set up code to listen to our broadcast
app.controller('SecondCtrl', function($scope){
$scope.$on('BOOM!', function(events, args){
console.log(args);
$scope.name = args; //now we've registered!
})
});
It's really that simple. Live Example
Other ways to achieve similar results
Try to avoid using this suite of methods as it is neither efficient nor easy to maintain but it's a simple way to fix issues you might have.
You can usually do the same thing by using a service or by simplifying your controllers. We won't discuss this in detail but I thought I'd just mention it for completeness.
Lastly, keep in mind a really useful broadcast to listen to is '$destroy' again you can see the $ means it's a method or object created by the vendor codes. Anyways $destroy is broadcasted when a controller gets destroyed, you may want to listen to this to know when your controller is removed.

The previous answer is a very good one. I'd like only to add a short remark: this $on kind of listener has a very important property: can be canceled (stoped).
I'll explain what do I mean:
The html:
<div ng-controller="FirstCtrl">
<input ng-model="name"/>
<button ng-click="register()">Register </button>
</div>
<div ng-controller="SecondCtrl">
Registered Name: <input ng-model="name"/>
</div>
<hr/><br/>
<button ng-click="disableEvents()">Disable events</button>
And the controller:
app.controller('SecondCtrl', function($scope){
$scope.cancelOn = $scope.$on('BOOM!', function(events, args){
console.log(args);
$scope.name = args; //now we've registered!
});
// this will cancel the event listening....
$scope.disableEvents = function(){
console.log('Canceling the event listener: ', $scope.cancelOn());
}
});
If you press the "Register" button, you can see it communicate with the second controller. Now press the "Disable events" button. This will cancel the listener, the $on(...). Now, if you press again the "Register", you'll notice that the listener is no longer listen for this kind of event.

Related

AngularJS | Handling $broadcast & $on for specific instance of the same controller

I have a utility controller build to manage documents attachments for reusing across my application.
<div ng-controller="someController">
<div ng-controller="documentController as temp1"></div>
<div ng-controller="documentController as temp2"></div>
</div>
Under the parent controller i.e. someController I have a broadcast method..
var module = angular.module("MyModule");
module.controller("someController",
function ($scope) {
$scope.$broadcast("callSomeFunctionInDocumentsController");
});
module.controller("documentController",
function($scope) {
$scope.$on("callSomeFunctionInDocumentsController", function() {
//do something here
});
});
Now the problem I am facing is that since the documentController is added twice to the view, the $on method is executed twice as well. Whereas based on some condition I would want to call the $on method either in temp1 or temp2 instance and not both.
I am not sure if what I wish to achieve is possible but any help will be much appreciated.
Thanks.
The $broadcast works simply: everyone who registered is notified through $on.
In your example, both controllers are registered.
So why do you use the same controller twice? Maybe worth to switch to component?
What about this one:
<div ng-controller="someController">
<div ng-controller="documentController as temp1"></div>
<div ng-if="oneCtrlGotNotification" ng-controller="documentController as temp2"></div>
</div>
where oneCtrlGotNotification is some flag (maybe under $rootScope).
So you will display second controller only when 1st already notified.
But it is a workaround.
One approach is to give a unique id to each element with a controller:
<div ng-controller="someController">
<div id="temp1" ng-controller="documentController as temp1"></div>
<div id="temp2" ng-controller="documentController as temp2"></div>
</div>
Then use the $attrs local to differentiate:
app.controller("documentController", function($scope, $attrs) {
$scope.$on("callSomeFunctionInDocumentsController", function() {
if ($attrs.id == "temp1") {
//do something specific to "temp1" controller
});
});
})
For more information, see
AngularJS Comprehensive Directive API Reference - controller
AngularJS $attrs Type API Reference

Change ng-show in another controller?

I want to change ng-show in another controller than ng-show is.
myApp.controller('popupCtrl', function() {});
myApp.controller('changePopup', function($rootScope){
// now i wanna show my Ppopup
$rootScope.popup = true;
});
<div ng-controller="popupCtrl">
<div ng-show="popup">
Popuptext
</div>
</div>
But this doesn't work... How can I fix it?
Thanks!
So first thing, you should never add to the $rootScope or change it in anyway. It has been optimised by the angular team.
Second thing, there is no need to involve the $rootScope.
Here is a demo showing how to communicate across two controllers.
The key is the event aggregator pattern:
Communicator.register(function (newValue) {
vm.value = Communicator.value;
});
I created a function in the Communicator to register a callback function. The aim is that when a value gets changed the callback function is fired off. I.e. an event is triggered (change event).
The second key part is fire that change event off:
Communicator.change(!Communicator.value);
Here we pass through to the change function a new value which will do two things:
Update the internal value so we can keep track of it
Loop through all the registered callbacks and execute them passing in the new value.
By implementing this pattern, we can minimise the extent to which we communicate around our application ($rootScope can have a tendency to traverse the scope heirarchy when you $broadcast).
Now we can follow more closely the principle of single responsibility. Our class is aptly named in its current scope, when we look at this factory we can tell it is supposed to "communicate".
Finally, with a global event aggregator pattern ($rootScope) it is far more difficult to keep track of where these events are being broadcast from, and where they'll end up. Here we don't have that issue
One way to solve this is to use $rootScope.$broadcast
Here is an example: http://plnkr.co/edit/EmJnZvXFRWv6vjKF7QCd
var myApp = angular.module('myApp', []);
myApp.controller('popupCtrl', ['$rootScope', '$scope', function($rootScope,$scope) {
$scope.popup = false;
$rootScope.$on('changePopup', function(event, data) {
$scope.popup = !$scope.popup;
});
}]);
myApp.controller('changePopup', ['$rootScope', '$scope', function($rootScope, $scope) {
$scope.changePopup = function() {
$rootScope.$broadcast('changePopup', 'data could be sent here');
}
}]);
View:
<div ng-controller="popupCtrl">
<div ng-show="popup">
Popuptext
</div>
<div ng-controller="changePopup">
<button ng-click="changePopup()">Change the popup</button>
</div>
Using a service/factory is a better solution for cross controller communication if you are working on a large application, but for a smaller app I would say using $broadcast, $emit and $on is sufficient.
Here is a working demo for you - sorry I changed the controller names, but I am sure you will be able to build on this. Good luck
angular.module('myApp', [])
.controller('c1', function($scope) {
// now i wanna show my Ppopup
$scope.popup = false;
$scope.$on('popup', function() {
$scope.popup = true;
});
})
.controller('changepopup', function($rootScope, $scope) {
// now i wanna show my Ppopup
$scope.clicker = function() {
$rootScope.$broadcast('popup')
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="c1">
<div ng-show="popup">
Popuptext
</div>
</div>
<button ng-controller="changepopup" ng-click="clicker()">Click me</button>
</div>

Access ng-model data outside of the controller

I have written the below code
<span ng-controller="calanderCtrl">
<input type="text" ng-model="onDate">
</span>
<pre>user.name = <span ng-bind="onDate"></span></pre>
I know its outside of the ng-controller so i am not able to bind the data, but my application requires calanderCtrl controller. I want to put this value to scope so that i can use it inside other controllers also. How do i do this?
You could use a publish subscribe pattern for this. That way you avoid putting the variable on the rootscope.
function Ctrl($scope) {
$scope.onDate = "12/01/2015";
$scope.$watch('onDate', function(newValue, oldValue) {
$scope.$emit('onDateChanged', newValue);
});
}
function Ctrl2($scope, $rootScope) {
$scope.onDate = "";
$rootScope.$on('onDateChanged', function(event, value) {
$scope.onDate = value;
});
}
Your controller will get called when your template loads.
<span ng-controller="Ctrl">
<input type="text" ng-model="onDate">
</span>
<pre>user.name = <span ng-controller="Ctrl2" ng-bind="onDate"></span></pre>
Now how does it work:
Angular does not share scopes. Each controller has its own seperate scope.
So in order to keep our child scopes up to date we need to somehow throw an event on which our children subscribe to. This can be done in two ways.
$scope.$emit or $rootScope.$broadcast
The difference between the two is subtle.
$scope.$emit wil send the event up the chain. so for instance consider this scope hierarchy.
rootscope
scope1 ---> subscribes to the emit $scope.$on
scope2 ---> performs a $scope.$emit
scope3 ---> subscribes to the emit $scope.$on
only scope1 will catch the event. since $scope.$emit goes up the chain.
this is a way to only update specific scopes. although what is mostly done is this.
rootscope
scope1 ---> subscribes to the emit $rootScope.$on
scope2 ---> performs a $scope.$emit
scope3 ---> subscribes to the emit $rootScope.$on
we inject $rootScope in the controller of scope1 and scope3 and subscribe to the emit on the rootscope. Since the rootscope is the highest scope it will always catch the $emit from scope2. This is a way to only send the event to specific controllers wich subscribe on the rootscope.
lastly we can also do this:
rootscope
scope1 ---> subscribes to the emit $scope.$on
scope2 ---> performs a $rootScope.$broadcast
scope3 ---> subscribes to the emit $scope.$on
we are now shouting on the rootscope and instead of moving up like emit, broadcast works down the chain. This is the equivalent of shouting in a room and everyone who doesnt have his ear protectors on will hear it. in essence everyone who subscribes on their local scope to the event that broadcast is sending

how to broadcast and emit events to other controllers in angular that have no parent-child relationship

I am trying to get hints from this post - Working with $scope.$emit and $scope.$on but nothing seems to work when the controllers are in no way related to each other.
That is -
<div ng-controller="CtrlA">
</div>
<div ng-controller="CtrlB">
</div>
and in CtrlB I would do something like this:
$rootScope.$broadcast('userHasLoggedIn', {})
and in CtrlA I would listen like so:
$rootScope.$on('userHasLoggedIn, function(event, data){});
And no - CtrlA never receives the broadcasted event unless I nest CtrlB div inside CtrlA div
Any idea?
It is tough to answer without knowing what you tried. Please see this plnkr:
http://plnkr.co/edit/hYzWOrgEyPLlCMZnDCo2?p=preview
I basically created two controllers, one sends some text to the other:
app.controller('CtrlA', function($scope, $rootScope) {
$scope.submit = function(){
$rootScope.$broadcast('userHasLoggedIn', $scope.input);
}
});
app.controller('CtrlB', function($scope) {
$scope.$on('userHasLoggedIn', function(event, data){
$scope.data = data;
});
$scope.data = 'nothing';
});

AngularJs: to Propagate event between directives by $emit

I have under one controller two directives :
<div ng-controller="ctrl">
<div data-dy-items-list items="items"></div> // FIRST DIRECTIVE
<div data-dy-subitems-list subitems="subitems"></div> //SECON DIRECTIVE
</div>
In the Second directive template, I have one button and in the directive.js file in the controller section I did this :
$scope.clickButton= function () {
......
$scope.$emit("UPDATE_PARENT","updated");
}
In the first directive, I would like to do this in the controller section:
$scope.update = false;
$scope.$on("UPDATE_PARENT", function (event,message){
$scope.update = true;
console.log('update: ' + $scope.update);
});
But it doesn't work!!!
$emit dispatches an event upwards through the scope hierarchy. Your directives are siblings and thus $emit won't work.
The solution might be to $broadcast an event from a parent scope. Do it from ctrl if that's an option for you, or inject $rootScope to the directive and do $rootScope.$broadcast from there:
$rootScope.$brodcast("UPDATE_PARENT","updated");
Mind that $broadcasting events from $rootScope might seem to be an anti-pattern for AngularJS. It strongly depends on the usecase. There are other solutions to your problem:
One of them is to create a parent directive for both of your directives.
Another one is to use an intermediatory service which will hold values. Then you can do $watch on the service data and react accordingly.
You can $emit the event to the ctrl and then ctrl will $broadcast it down to the other directive.
Choose whatever fits your needs best.
I just got same problem.
Event between two directives was now passed, not with scope.$emit, not with scope.$broadcast.
After looking around, I did this trick,
Use scope.$parent.$broadcast with your $parent scope:
Directive 1:
scope.$parent.$broadcast('NO_LIVE_CONNECTION', {});
Directive 2:
scope.$on('NO_LIVE_CONNECTION', function (event, params) {
console.log("No live DB connections ...");
scope.state.showErrorMessage = true;
});

Resources