I've a main controller and into it two other controllers:
<div ng-controller="mainController" ng-model="value">
<div ng-controller="otherController"></diV>
<div ng-controller="anOtherController"></div>
</div>
Thanks to the controller inheritance my otherController can update my value I bind with the ng-model diretive.
But how can I notice my anOtherController that the value have changed in order to execute a function owned by anOtherController?
Can I register a function to this value?
You can do this in your controller..
$scope.$watch("value",function(newValue,oldValue){
// your code goes here...
});
It will basically watch for any changes on a given "scope property". However, my advice is to use a either a service or a factory.
Please refer to this other SO Discussion:
AngularJS Service Passing Data Between Controllers
Send a message to the other scopes:
$scope.$broadcast('messagename', params);
To catch it:
$scope.$on('messagename', function(params){
alert('something happend');
});
The scope inherits downwards; if you want the message to be sent to all scopes, use $rootScope.$broadcast.
Or you can simply add a watcher.
You need to be careful here:
mainController will have a scope.value
otherController and anOtherController won't have a scope.value defined, although if you try to access scope.value it will work since they are children of mainController so it will transverse the prototype chain to find that property. However, when you assign a value into scope.value in otherController/anOtherController , you are defining a local version of that variable in your scope and it will differ from the other controllers.
If you genuinely want to modify the same element for the three elements you will need to assign new values to the parent scope property in the children controllers.
For example:
if scope.value was equal to "old" in maincontroller:
in otherController:
$scope.$parent.value = "new"
in anOtherController or mainController:
console.log($scope.value) // "new"
But if you do:
in otherController:
$scope.value = "new"
in anOtherController or mainController:
console.log($scope.value) // "old"
Now, knowing all this and answering your question if you want to execute a function everytime scope.value changes in anOtherController and you are not going to modify that property in that scope you can get away with:
Inside anOtherController:
$scope.watch('value', function(newVal, oldVal) {
myfunc();
}
But you need to make sure that you don't assign values to $scope.value instead assign them to $scope.$parent.value.
Related
I need to require a property from a controller from another directive in my main directive, which is easy:
return {
...
require['myOtherController'],
link: function(scope,element,attrs,controller){
scope.propFromOtherCtrl = controller[0].propFromOtherCtrl;
}
But: the controller of my directive gets loaded first.
So at first, propFromOtherCtrl is undefined in the controller until the link function got executed.
Right now, i am $scope.$watching the property in my controller until it is defined and then kick off the initialization of my controller manually.
//directive controller
$scope.$watch(function(){return $scope.propFromOtherCtrl; },function(n,o){
if(n !== o){
// init
}
});
But that seems rather hacky. Is there a more elegant way of doing this?
I can not share the scope between the two directives directly because of the architecture of my app.
If the architecture is the only reason you cannot share the scope between two controllers, you can always use $controller to inherit one controller from the other, then you would have access to its scope regardless of the location in the html dom:
$controller('myOtherController', { $scope: $scope });
Another alternative is inserting the html part that triggers the directive into an ng-if that doesn't get initialized until the other controller is ready:
ng-if="propFromOtherCtrlLoaded"
Finally, in case neither of these suit you, using $watch is not that bad, except a small addition, stopping to listen to the changes, would make it more efficient:
var unreg = $scope.$watch(function(){return $scope.propFromOtherCtrl; },function(n,o){
if(n !== o){
// init
unreg();
}
});
I just started AngularJS a few days ago and I've read that scopes are immediately updated whenever the value of their linked element changes. I have this HTML code with my controller:
<div ng-controller="lyricsMod">
<textarea ng-model="valueB"></textarea>
{{valueA}}
And my AngularJS controller:
myMod.controller('lyricsMod', function($scope) {
$scope.valueA = $scope.valueB;
});
However, this outputs nothing. But, changing the HTML code to:
<div ng-controller="lyricsMod">
<textarea ng-model="valueA"></textarea>
{{valueA}}
Produces the wanted result. Pretty sure it has nothing to do with the AngularJS and that its just linking two things together in HTML. I don't understand, if the scope is immediately updated, why is this not working?
try it:
u need to make use of $watch if you want change scope variable on change of other scope variable
myMod.controller('lyricsMod', function ($scope) {
$scope.$watch('valueB',function(){
$scope.valueA=$scope.valueB;
});
$scope.valueA=$scope.valueB;
});
scopes are immediately updated whenever the value of their linked
element changes
Yes but only scope variables bound using ng-model. In your fist example ValueB is bound using ng-model not valueA.If you want to change value of another scope variable when another changes(and its bound using ng-model). Use ng-change directive.
<div ng-controller="lyricsMod">
<textarea ng-model="valueB" ng-change="changeA()"></textarea>
{{valueA}}
$scope.changeA = function () {
$scope.valueA = $scope.valueB;
}
In my application I would like to preserve the option of using plain controllers for certain sections of code - as opposed to creating directives for one-off things that will never be re-used.
In these cases I often want to publish some data from the controller to be used in the contained section. Now, I am aware that I could simply bind items in the controller's scope, however I'd like to specify the "model" location explicitly just to make the code more maintainable and easier to read. What I'd like to use is ng-model as it would be used on a custom directive, but just along side my plain controller:
<div ng-controller="AppController" ng-model='fooModel'>
{{fooModel}}
</div>
However I can see no way to get a reference to the generated ngModelController without using a directive and the 'require' injection.
I am aware that I could make my own attribute fairly easily by injecting the $attr into my controller and do something like:
<div ng-controller="AppController" my-model='fooModel'>
{{fooModel}}
</div>
In which case I just manually take or parse the myModel value and stick my model into the $scope under that name. However that feels wrong in this case - I really only need one "model" for a controller and I'd prefer not to have to add this boilerplate to every controller when ngModel exists. (It's the principle of the thing!)
My questions are:
1) Is there some way to use ngModel along with a plain controller to get the effect above?
2) I have been trying to figure out where ngModelControllers are stored so that I could look at the situation in the debugger but have not been able to find them. When using an ngModel directive should I see these in the scope or parent scope? (Where do they live?!?)
UPDATE: As suggested in answers below $element.controller() can be used to fetch the controller. This works (http://plnkr.co/edit/bZzdLpacmAyKy239tNAO?p=preview) However it's a bit unsatisfying as it requires using $evalAsync.
2) I have been trying to figure out where ngModelControllers are stored so that I could look at the situation in the debugger but have not been able to find them. When using an ngModel directive should I see these in the scope or parent scope? (Where do they live?!?)
The answer depends slightly on where you want to access the controller from.
From outside the element with ng-model
It requires "name" attributes on both the element with the ng-model attribute, and a parent form (or ngForm). So say you have the form with name myForm and the element with ng-model attribute with name myInput, then you can access the ngModelController for myFoo from the parent scope as myForm.myInput. For example, for debugging purposes:
<p>myFoo: {{myForm.myInput.$modelValue}}<p>
<form name="myForm">
<div ng-controller="InnerController" name="myInput" ng-model="model.foo"></div>
</form>
as can be seen at http://plnkr.co/edit/IVTtvIXlBWXGytOEHYbn?p=preview
From inside the element with ng-model
Similar to the answer from #pixelbits, using $evalAsync is needed due to the order of controller creation, but you can alternatively use angular.element.controller function to retrieve it:
app.controller('InnerController', function($scope, $element) {
$scope.$evalAsync(function() {
$scope.myModelController = $element.controller('ngModel');
});
});
Used, inside the controller to view it, for debugging purposes, as:
<div ng-controller="InnerController" ng-model="model.foo">
<p>myFoo: {{myModelController.$modelValue}}<p>
</div>
As can be seen at http://plnkr.co/edit/C7ykMHmd8Be1N1Gl1Auc?p=preview .
1) Is there some way to use ngModel along with a plain controller to get the effect above?
Once you have the ngModelController inside the directive, you can change its value just as you would were you using a custom directive accessing the ngModelController, using the $setViewValue function:
myModelController.$setViewValue('my-new-model-value');
You can do this, for example, in response to a user action that triggers an ngChange handler.
app.controller('InnerController', function($scope, $element) {
$scope.$evalAsync(function() {
$scope.myModelController = $element.controller('ngModel');
});
$scope.$watch('myModelController.$modelValue', function(externalModel) {
$scope.localModel = externalModel;
});
$scope.changed = function() {
$scope.myModelController.$setViewValue($scope.localModel);
};
});
Note the extra watcher on $modelValue to get the initial value of the model, as well as to react to any later changes.
It can be used with a template like:
{{model.foo}}
<div ng-controller="InnerController" ng-model="model.foo">
<p><input type="text" ng-model="localModel" ng-change="changed()"></p>
</div>
Note that this uses ngChange rather than a watcher on localModel. This is deliberate so that $setViewValue is only called when the user has interacted with the element, and not in response to changes to the model from the parent scope.
This can be seen at http://plnkr.co/edit/uknixs6RhXtrqK4ZWLuC?p=preview
Edit: If you would like to avoid $evalAsync, you can use a watcher instead.
$scope.$watch(function() {
return $element.controller('ngModel');
}, function(ngModelController) {
$scope.myModelController = ngModelController;
});
as seen at http://plnkr.co/edit/gJonpzLoVsgc8zB6tsZ1?p=preview
As a side-note, so far I seem to have avoided nesting plain controllers like this. I think if a certain part of the template's role is to control a variable by ngModel, it is a prime candidate for writing a small directive, often with an isolated scope to ensure there are no unexpected effects due to scope inheritance, that has a clear API, and uses require to access the ngModelController. Yes, it might not be reused, but it does help enforce a separation of responsibilities between parts of the code.
When you declare directives on an element:
<div ng-controller="AppController" ng-model='fooModel'>
{{fooModel}}
</div>
You can retrieve the controller instance for any directive by calling jQlite/jQuery $element.data(nameOfController), where nameOfController is the normalized name of the directive with a $ prefix, and a Controller suffix.
For example, to retrieve the controller instance for the ngModel directive you can do:
var ngModelController = $element.data('$ngModelController');
This works as long as the ngModel directive has already been registered.
Unfortunately, ngController executes with the same priority as ngModel, and for reasons that are implementation specific, ngModel is not registered by the time that the ngController function executes. For this reason, the following does not work:
app.controller('ctrl', function ($scope, $element) {
var ngModelController = $element.data('$ngModelController');
// this alerts undefined because ngModel has not been registered yet
alert(ngModelController);
});
To fix this, you can wrap the code within $scope.$evalAsync, which guarantees that the directives have been registered before the callback function is executed:
app.controller('ctrl', function ($scope, $element) {
$scope.$evalAsync(function() {
var ngModelController = $element.data('$ngModelController');
alert(ngModelController);
});
});
Demo JSFiddle
I have a custom directive that has a single attribute called content. I'm trying to pass data to this directive's attribute via an outer's controller scope variable called x. This seems to work fine by setting x equal to 'xyz' at the start of the controller, but when I make a call to a factory to pull a value to update the x variable the data isn't being reflected in the directive. I can see that $scope.x is being set to the return value from the factory within the controller, but it's not then updating the directive.
I seem to missing something somewhere. I'm guessing it's a scope problem but I'm just not seeing it. I've tried to simplify my issue into a Plunker (http://plnkr.co/edit/wsWzSTJ9VDprfTaToeHv).
Any thoughts?
Thanks
Chris
The basic problem as noted in the comments is a missing $watch in the directive controller.. the controller doesn't "know" that content variable has changed..
so adding a $watch in the controller (the directive controller!) would solve this:
$scope.$watch(function(){return $scope.content}, function(){
$scope.text = $scope.content
})
A working demo
Good luck!
I have the following $rootScope variable which I use to save the current logged in user privilege level, then I access this variable from other controllers. Is there a way I can watch the rootScope variable for changes in order to update controllers specific variables with any changes to the root scope variable? Below is the code I am using so far, can someone please tell me what I am doing wrong and how to fix it? Thanks
In app.js under .run:
$rootScope.uPLevel = 0;
.controller
$scope.$watch($rootScope.uPLevel, function() {
$scope.userPLevel = $rootScope.uPLevel;
}, true);
The first parameter to $watch should either be a string or a function (docs). Right now you're passing it the value of $rootScope.uPLevel on controller initialization.
$scope.$watch(function() {
return $rootScope.uPLevel;
}, function() {
$scope.userPLevel = $rootScope.uPLevel;
}, true);
Two sidenotes:
It may be prudent to store this value in a service instead of $rootScope.
If uPLevel is only an integer (as your example suggests) then you don't need to pass true as the third parameter - that's only for arrays and objects. If you do want to watch a collection, then I suggest using $watchCollection instead.
I recommend watching $rootScope variables like that:
$scope.$watch('$root.uPLevel', function() {
$scope.userPLevel = $rootScope.uPLevel;
});
This way, When current directive/controller is destroyed. It clears the watch as well. In $rootScope.$watch case, the watch stays forever.