Access ng-model data outside of the controller - angularjs

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

Related

Bind scope variable from controller to directives without using $watch

For Angular 1, I have an AJAX call that is made in the controller (as it is dependent on another set of data that is returned during the route resolve) and once the response is back, the data is passed down to directives. Since the data doesn't come back until after the directives are compiled, the directives initially gets an undefined for that data passed from the controller and would only get the data if I am watching that scope value inside each of the directive. Is there a better way where I don't have to use $scope.$watch or any event listeners, such as $broadcast/$on? I don't want to exhaust the digest cycle with too much watchers.
Here's a mock structure:
<parent>
<directive1 data="manipulateDataReturnedFromAJAXCall"></directive1>
</parent>
//template for directive1
<div>
<directive2 ng-if="data" attr1="data.field1"></directive2>
<div>
What about using a callback in your http service.
//Controller
MyHTTPService.getData(function(data){
$scope.manipulateDataReturnedFromAJAXCall = data;
})
//MyHTTPService service
return{
getData : function(fct){
$http.get("url/to/data").then(function(response){
fct(response.data);
})
}
}

Changing $scope from inside $rootScope is not getting reflected

I am trying to show one button as in this Plunker
<div ng-show="showbtn"><button class="fix btn btn-success" ng-click="top()">To the top</button></div>
On scroll event, I have made $rootScope.$emit call and it is getting triggered too, but not sure why the $scope value is not getting changed inside the mainCtrl controller $scope. Is $scope inside $rootScope is different ?
The event handler (the function passed to $rootScope.$on) runs outside of Angular's normal digest cycle so you need to tell the parent scope that something has changed. You can use $apply to do so:
$rootScope.$on('scrolled',function(event,data){
$scope.$apply(function () {
$scope.showbtn = data.message;
});
});
Here's an updated Plunker.

What is .$on() in 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.

Directive handling event from model

I have a CurrentUser model class that, when the user is not authenticated, I want to throw a 'NOT_AUTHENTICATED' event.
On certain pages, I want to use a directive that will handle this event and display a modal.
Will emiting events from a model get bubble up to the directive link scope?
Angular events are triggered by and received by scopes. So, you need a $scope object to broadcast an event and you need a scope object to listen to an event. If by "model" you mean an angular service, then you can inject the $rootScope and $broadcast the event from there like this...
myApp.factory('theModel', function($rootScope) {
$rootScope.$broadcast('NOT_AUTHENTICATED');
});
The event will bubble down the scope chain and can be heard by your directive's scope in a link function..
myApp.directive('theDirective', function () {
return {
...
link:function(scope, element, attrs) {
scope.$on('NOT_AUTHENTICATED', function (event) {
...
}
}
};
});

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