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;
}
Related
The Issue
Using my directive within another directive causes certain bindings to be lost, specifically in the ng-repeat usage.
The directive is used in many areas of my application without issue. It renders a list of inputs which is passed to it's scope from a parent template, like so:
<filter-header filters="filters"></filter-header>
Working Scenario
I have used the following scenario throughout the application and not come across an issue to date.
$routeProvider resolves filters list with WebAPI call for a controller
controller assigns list to its own scope, like so: $scope.filters = filters
template uses filter-header element and passes filters from it's scope to the directive, like so: <filter-header filters="filters"></filter-header>
The filter-header directive then renders the filters using an ng-repeat without issue. $$hashKey is present in each item of the filters collection, indicating the binding's existence.
Failing Scenario
In the following scenario, the binding seems to be lost and the ng-repeat fails to render anything.
$routeProvider resolves filters list with WebAPI call for a controller
controller assigns list to its own scope, like so: $scope.filters = filters
template uses a new element directive, assigns filters from it's scope to the new directive's via an attribute.
directive's template uses filter-header element and passes filters from it's scope to the directive, like so: <filter-header filters="filters"></filter-header>
The filter-header directive then FAILS TO render the filters using an ng-repeat. $$hashKey is NOT present in any item of the filters collection.
Annoyingly, I cannot replicate this in Plunker...
Oddity
The directive has another collection of item's passed to it, columns="columns" (can be seen in the code below). Columns binds correctly and is rendered in it's own ng-repeat. I cannot see how Columns is different from Filters as both are used almost exactly the same way.
Looking Deeper...
I have debugged the process all the way. The filters object is getting all the way to the end scope successfully. If I output the contents of filters to the screen within the final directive, using {{ filters }} I can see all of the filters as expected. However, in the very next line where my ng-repeat begins, no filters are iterated through.
To be certain it is not my list causing issues, I used a list that already works using the working scenario mentioned above and the ng-repeat does not render here.
To be certain it is not my directive's code causing issues, I converted it to a controller and routed directly to it (skipping the nested directive) as in the working scenario mentioned above and the ng-repeat now works.
Using $log to inspect the list, I notice one difference. In the working scenario, all lists contain a $$hashKey property for each item in the list. In the failing scenario, the $$hashKey is missing on all items in the list. This seems to indicate that the binding is being lost for some reason.
Can someone tell me the error in my ways? The only real difference I can see in my usage is that I pass the object to an middle-man directive before before passing it on the the directive where it is used. Strangely, in the very same directive, another list is used in a very similar way and it renders without issue within it's ng-repeat, and it's item's all have the $$hashKey property appended.
Code
There's a lot of code involved, so I'll try and pick out the relevant parts.
RouteProvider
$routeProvider.when('/Pride/Admin/AuditForms/:id', {
templateUrl: '/Templates/Admin/editAuditForm.html',
controller: 'editAuditFormController',
resolve: {
sectionFilters: function (auditFormSectionRepository) {
return auditFormSectionRepository.getFilters().$promise;
},
sectionColumns: function (auditFormSectionRepository) {
return auditFormSectionRepository.getColumns().$promise;
}
}
});
EditAuditForm Controller
prideModule.controller("editAuditFormController", function ($scope, sectionFilters, sectionColumns) {
$scope.sectionFilters = sectionFilters;
$scope.sectionColumns = sectionColumns;
});
EditAuditForm Template
<audit-admin-sections audit-form="auditForm" section-filters="sectionFilters" section-columns="sectionColumns" show-deleted="false"></audit-admin-sections>
AuditAdminSections Directive
prideModule.directive('auditAdminSections', function ($log) {
return {
restrict: 'E',
templateUrl: 'templates/admin/auditFormSections.html',
scope: {
sectionFilters: '=',
sectionColumns: '='
},
controller: function ($scope, $route, $timeout, $location, filterLogic, auditFormSectionRepository) {
// do stuff
}
});
AuditFormSections Template
<filter-header filters="sectionFilters" columns="sectionColumns"></filter-header>
FilterHeader Directive
prideModule.directive('filterHeader', function($log) {
return {
restrict: 'E',
templateUrl: 'templates/common/filterHeader.html',
scope: {
filters: '=',
columns: '='
},
controller: function ($scope, filterItemsRepository) {
$log.info("$scope.filters");
$log.info($scope.filters);
// This will log the filters as expected, however the $$hashKey property is missing from the items
}
});
FilterHeader template
<!-- at this point, {{ filters }} produces the list of filters -->
<form class="form-horizontal" ng-repeat="filter in filters">
<!-- at this point, nothing renders -->
<label class="col-sm-2 control-label">{{ filter.friendlyName }}</label>
</form>
Update 1
I pulled the code out of the directive and into a new controller to mimic the Working Scenario mentioned above. The ng-repeater now functions as expected and the $$hashKey is present again. So something is definitely related to the difference between route->controller->directive->directive vs route->controller->directive.
Worth mentioning, on top of the code above, there are watches on the filters among other usages.
Update 2: Offender Uncovered
I've nailed it. But it makes no sense yet. It seems as though the form element is to blame. Changing to a div resolves the issue. I'm thinking this may be an angular bug as I'm struggling to see why this could work in one scenario and not the other.
Have discovered two fixes so far, but they are more hacks than fixes as I cannot understand the cause of the original problem. I'm still interested if anyone can point out the real issue, but until then this is the best I have:
Solution 1
After much researching, debugging, head scratching, rewriting, I by luck came across a solution. I'm not happy with it, as it doesn't make sense (unless someone can elaborate for me).
The problem seems to be with the form element and using the ng-repeat attribute on it while being nested in angular directives...!!! This has to be a bug, right?
Solution was as simple as changing this:
<form class="form-horizontal" ng-repeat="filter in filters">
<label class="col-sm-2 control-label">{{ filter.friendlyName }}</label>
</form>
to this:
<div class="form-horizontal" ng-repeat="filter in filters">
<label class="col-sm-2 control-label">{{ filter.friendlyName }}</label>
</div>
Solution 2
It seems that the $scope variables were also playing a part in the issue. If I rename each directive's $scope variable name for this particular variable (ie, filters) so that it is unique for each directive, the form ng-repeat works. This makes it seem like there is some sort of conflict within the directives' isolated scopes, however why this is only an issue for form ng-repeat baffles me. As such, it still doesn't explain to me what is the root cause of this behavior.
I'm beginner to AngularJs but now I have used and understand how AngularJs works quite a bit.
The thing which I want to ask about is the few lines in the ngModel directive documentation.
These lines are:
Note: ngModel will try to bind to the property given by evaluating the
expression on the current scope. If the property doesn't already exist
on this scope, it will be created implicitly and added to the scope.
I don't understand what they are trying to say. I do know that the ngModel directive binds a property to the input, select and textarea controls. Just like a very simple code below:
Name: <input type="text" ng-model="myName">
{{myName}}
So, can anybody come up with any other precise example which helps me to understand those lines?
ngModel is a standard way that Angular binds a scope property (usually declared in a controller) to the UI.
So, typically, a controller is created with the property declared inside:
angular.controller('Ctrl', function($scope) {
$scope.myProperty = '';
});
And you would bind it to the UI like this:
<div ng-controller="Ctrl">
<input type="text" ng-model="myProperty"/>
</div>
In this example $scope is scoped in the UI on the div element because the ng-controller attribute binds the Ctrl to that element.
However, Angular allows you to be a bit lazy as well and not bother defining the myProperty in the controller:
angular.controller('Ctrl', function($scope) {
});
And still using it in the UI:
<div ng-controller="Ctrl">
<input type="text" ng-model="myProperty"/>
</div>
In this case Angular will implicitly (dynamically) create a myProperty on $scope. That can be used in the UI inside the ng-controller's scope.
This can be used for UI only properties that you don't want to bother the controller about. An example would be when you want to hide/show something based on a user's interaction.
Hope this helps.
You have a controller with a $scope.
You can init your $scope with values(e.g.):
$scope.myName= 'test';
And then you input will have a default value of test.
Name: <input type="text" ng-model="myName">
{{myName}}
You can choose not to init your scope with the value, and then $scope.myName will be created behind the scenes and bound with no default value.
Let's break this down:
Note: ngModel will try to bind to the property given by evaluating the expression
on the current scope. If the property doesn't already exist on this scope, it
will be created implicitly and added to the scope.
1) ngModel will try to bind to the property given by evaluating the expression on the current scope.
So if you have the element:
<input ng-model="theProperty">
This element will have a scope:
var theScope = element.scope()
Most of the times this scope is accessed in a controller constructor... now, let's go back to the statement:
will try to bind to the property given... on the current scope.
Note that it says "try" because it is possible that the current scope contains the property or not. Because angular will do something like this:
theScope['theProperty'] = the_inputs_value;
When the property did not exist, the property gets created. So this explains the next statement.
If the property doesn't already exist on this scope,
it will be created implicitly and added to the scope.
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 an select field with an ng-model attribute in a tab within ui-bootstrap's tabset. There is also a button on the tab. On click of the button I would like to get the selected value in the model. I tried using
<select ... ng-model="selectedOption"></select>
<button ng-click="buttonClick()">Click</button>
and then in the controller
$scope.buttonClick = function() {
//try to access $scope.selectedOption
}
But that does not seem to contain the value. I tried looking into the $scope variable and it seems to contain the selectedOption inside something called $$childTail
Is there any other way to access the selectedOption value or should I change the structure of my view?
I have a Plunker here of what I'm trying to do.
Both Mathew Berg's and Bumblebee Man's answers should work but they do not explain what's happening.
Okay basically it goes down like this:
The tabset directive that you are using does transclusion so the selectedOption is actually in an inner scope(tabset's scope), not in your TabsDemoCtrl's cope. This answer explains what transclusion does, and how you can access an transcluded model.
Transclusion makes shallow copies of your models, so even if you init selected option like $scope.selectedOption = "5"; in your TabsDemoCtrl, your select will show 5 as selected at the begining but changes will not be reflected to your TabsDemoCtrl controller's scope because in the tabset's child scope 5 is just copied, and the one being updated is in the tabset's scope not your scope.
You can also use an object for your select's model. Since the transclusion creates a shallow copy, objects/arrays/functions are copied by reference and variables are copied by value. This is also why you can access your TabsDemoCtrl's button click from the inner scope.
updated plnkr with another alternative solution(the object version) :
created an object in your controller
$scope.selection = {};
and used in model like this
ng-model="selection.option"
http://plnkr.co/edit/5sI0arorV9dULzaDxRra?p=preview
Angular ui tab/tabset directive create new scopes, so in order to access the parent scope (What you're trying to do) just attach $parent
<select name="selectedCampaign" ng-model="$parent.selectedOption" ng-options="option for option in options"></select>
Updated plunkr: http://plnkr.co/edit/BgMMn4tJRWm72ZXeQG3N
Edit: Apologies, I misread the question, can't you just do this?
<select ... ng-model="selectedOption"></select>
<button ng-click="buttonClick(selectedOption)">Click</button>
$scope.buttonClick = function(selectedOption) {
selectedOption.whatever...
}
The docs specify that you can expost the form to the scope with name. so lets say I have this form:
<form name="myForm" ng-submit="submit()">
<input type="text" ng-model="somemodel"/>
<button ng-submit="submit()"></button>
</form>
but I'm trying to access the form through the controller with $scope.myForm and fail:
$scope.submit = function(){
console.log($scope.myForm) // or form[0] or scope.myForm[0] etc..
if(!$scope.myForm.$dirty){
//do this and that
}
//do something
}
they all fails as undefined. how is this done? ultimately I would like to also call $setPristine() from the controller. but I just can't find out where the form is hiding. is he even available to the controller or just in the view scope?
using angular 1.2.5
EDIT: this doesn't work also when the form name is with the dot notation: myForms.myform
Another edit: after forking the suggested plunker I found out that the form doesn't exists on the $scope before submitting, but does exist after.
So I guess the refined question should be: is this the expected behavior? Is there a workaround this (in the ctrl and not in a directive)?
If this is expected and no workaround so I'll move the custom validation checks to specific directives - since in the directives (if they require:"^form") the form is available before submit.
Thanks!
Answer this issue seems to be when the form is added to the scope. from the plunkers it's obvious that not in the beginning, but attached later, after the Parent Ctrl got loaded. can we control the loading order? I don't know. seems the place for this kind of form scope manipulation should be a directive and not the ctrl
One reason you might not be able to see the form on your Controller $scope is if your <form> is inside a directive that introduces a new child scope. e.g. if the form is inside an ng-if or ng-switch.
In other words, in the example below, myForm ends up getting published to the child scope introduced by the ng-if and you won't be able to see it on the MyController $scope.
<div ng-controller="MyController">
<div ng-if="true">
<form name="myForm"></form>
</div>
</div>
One way to deal with this is to use dot-notation to publish the form into an object wrapper instead of putting it directly on the scope.
<div ng-controller="MyController">
<div ng-if="true">
<form name="myStuff.myForm"></form>
</div>
</div>
And in MyController, make sure an object is there so it will publish to it instead of the child scope.:
$scope.myStuff = {};
Take a look at this egghead.io video that does a good job explaining this: https://www.youtube.com/watch?v=DTx23w4z6Kc
If you change the reference from $scope.form to $scope.myForm, then that is defined, as in this plunker. Unless you have a parent form called myForms that you haven't mentioned, you won't be able to access it as $scope.myForms.myform.
Edit: the refined question asks whether it's expected behaviour that on the form's parent controller that the form is undefined when the controller is first run. The answer is yes, it's not added until later.
The question suggests that the form is only defined when the form is submitted. This isn't quite accurate. The form is added on the scope before this: there is nothing special about the submit function. You should be able to quite happily call form functions in response to other button presses. In the parent controller you can have
$scope.makeTheFormPristine = function() {
$scope.myForm.$setPristine();
}
As can be seen in the following Plunkr:
http://plnkr.co/edit/dDVrez9UP1GYRIDrUxJs?p=preview
Edit: from the comments, if you want a variable in the parent scope, say notification, to depend on a property of the form, you can setup a manual watcher:
$scope.$watch('myForm.$pristine', function(newValue, oldValue) {
if (newValue) {
$scope.notification = 'Pristine';
} else {
$scope.notification = 'Not pristine'
}
});
You can see this at http://plnkr.co/edit/2NMNVAfw8adlpFRGuSoC?p=preview . However, I would be a bit careful about putting too much "view" logic into the controller. It might be better placed in the template, or maybe event a custom directive. This is probably beyond the scope of this question, and maybe better on https://codereview.stackexchange.com/.
Also: I don't think you need the 2 ng-submit attributes, both on the button and the form, but I suspect that isn't related to this issue.
Oops. Just re-read your Question and realized that you are using the wrong scope path. As already indicated by Matt, the form will be pusblished to $scope.myForm and not $scope.form.myForm.