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.
Related
Here is my Plunk
I need to understand how AngularJS handles scope variable and method part of given scope.
Below is my controller code
var app = angular.module('plunker', []);
app.controller('MainCtrl', ["$scope", function($scope) {
$scope.name = "";
$scope.getNameLength = function(){
return $scope.name.length;
}
}]);
Here is my html body (just keeping my div for simplicity)
<body ng-controller="MainCtrl">
<div>
Enter Your Name :<input type="text" ng-model="name">
<br>
{{ "Your entered name whoes length is = " + getNameLength() }}
</div>
</body>
As and when i enter something in the text box, the getNameLength() is called and the DOM is updated to reflect the name's length.
As long as the method being referenced in a directive the method is called whenever there is a change in the name.
Here is my doubt:
Why angular calling all the method in the scope (which are being referenced in directive) whenever there is a change in view model? is it possible to disable this behavior? Are there any performance implication in this?
If you are concerned that Angular is calling your method too many times and you want to limit the execution, you can always use the ngModelOptions directive and pass in debounce. You can see the documentation on the AngularJS page. For example:
<input type="text" ng-model="name" ng-model-options="{debounce: 500}">
Will only update the model once the model has stopped updating for 500 milliseconds. You could also use something like ng-model-options="{updateOn: 'blur'}" to only update the model after the field has lost focus.
As far as performance is concerned, if it is something simple like calculating a string's length, you shouldn't have too much to worry about. If it is something more complex, you could run into issues.
Why angular calling all the method in the scope (which are being
referenced in directive) whenever there is a change in view model?
I do not see a custom directive in your example, but Angular directives will either inherit scope properties from their parent, use the parent scope, or have an isolate scope.
If you do not have an isolate scope, it will look for the property in the parent's scope unless you override it.
Because you have an Angular expression (the {{ and }} surrounds it), Angular makes a watcher for whatever is in the expression. When it detects a watched variable or object has changed, it will update all things dependant on it.
is it possible to disable this behavior?
Yes, indeed, as mentioned by 'YOU' in the comment to your question, you can use a 'one time binding'.
Example:
{{normalBinding}}
{{::oneTimeBinding}}
Are there any performance implication in this?
Yes, the more bindings you have, the more watchers, the more the digest cycles will take, the longer it will take for your application to reflect changes. This is a concern for big applications.
More information about the scope, and watchers, can be found here.
I have a simple form that looks like this:
<form name="myForm">
<input name="field" ng-model="item.field"></input>
<button ng-click="save()">Save</button>
</form>
In theory, I should be able to change things like validity of fields in the controller based on the form and field, as such:
$scope.save = function(item) {
item.$save();
$scope.myForm.field.$setValidity("some-error",false);
};
The problem is that directives at various levels get in the way and may set up multiple child scopes. So while item.field is properly rendered and linked, my ability to do anything from the controller is very limited, e.g. if I wrap my partial in a fancy "loading" directive that hides it and shows "loading" until done.
<loading>
<form name="myForm">
<input name="field" ng-model="item.field"></input>
<button ng-click="save()">Save</button>
</form>
</loading>
Now my controller that wanted to directly set validity no longer has access to $scope.myForm. Truth is, with possible directives, it never can safely rely on access to $scope.myForm; only a directive can!
#Beyers recommends passing the form as part of the save() call, as <button ng-click="save(myForm)">Save</button>, which definitely works, but gets cumbersome.
Is there a real Angular way to do this cleanly? Should I be resetting to $pristine inside my controller directly, or is there some other thing to do?
UPDATE:
I am beginning to wonder if this is the right approach at all. Should my controller be doing this like $setValidity on a form? Shouldn't it take input from the view and modify business objects (item) on that basis, as well as interact with services? How does angular know to reset ng-dirty or ng-invalid for its own validations?
The problem is that directives at various levels get in the way and may set up multiple child scopes.
You're hitting the standard if you don't have a dot in your model, you're doing it wrong issue. This isn't just true for ng-model, but also for form name.
A solution is to make sure you have an object in your controller for forms, such as forms:
$scope.forms = {};
In the template use forms.<formName>:
<form name="forms.myForm">
...
</form>
And then in the controller code to access the form, use $scope.forms.<formName>
$scope.forms.myForm.field.$setValidity("some-error" ,false);
And your updated question:
Should my controller be doing this like $setValidity on a form
I would address this on a case-by-case basis in order to KISS. Validity is often the result of interacting with services, and can be related to business objects/rules. A little bit of code in the controller interacting with validation I would say is ok, but if your controller is getting a bit long with a quite a few responsibilities, then it might then be a good idea to try to move some of the logic into custom directives on the elements that require ngModel. These can then accept parameters/functions via attributes, set by the controller, if required.
I just grabbed the form field by Id last time I did this and it worked fine.
Using the ID, you "Angularize" it and then you can access it's model etc...
Like this:
//you will need to add an id to your field.
var myInput = angular.element(document.querySelector('#myInput'));
var ngModelCtrl = myInput.controller('ngModel');
ngModelCtrl.$setValidity("some-error",false);
The angular docs say that you should only do this when you have to, they prefer you to use the ngModel.$validators pipline. Unfortunately I'm not sure how to use that myself so can't help you there.
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
There is clearly something fundamental im not yet understanding.
Im trying to make user of the Modal module in Angular Ui.Bootstrap but im finding that my clicks are not activating the open() function -- So boiling it down to a very simple testcase, as below, im not seeing any calls when the ng-click points to a function (alert or console.log), but does work when the ng-click points to something which is just an expression
Why is the alert not called in the first example?
<div data-ng-app>
<button data-ng-click="alert('Message 1');">
ngClick -- not working, why not?
</button>
<button onclick="alert('Message 2');">
Plain onclick
</button>
<button data-ng-click="count = (count + 1)">
But this works, why ???
</button>
count: {{count}}
</div>
http://jsfiddle.net/H2wft/1/
ng-click is meant for use with either a function in the current scope (so for example $scope.alert = window.alert would solve the problem of not being able to alert there) or an angular expression. it looks like angular does not allow you to use global scope methods in there (it might be looking them up in the current $scope, from which they are missing).
ng-click expects an angular expression. Internally, angular is using the $parse service to evaluate the expression in ng-click="expression".
An angular expression is not the same as regular javascript code. $parse uses string parsing to interpret the expression and it restricts your access to variables, functions, and objects to just those which are properties of the $scope object, or properties of any $parent scope objects which happen to be available further up the prototypical inheritance chain.
So in theory you could gain access to globals like this:
$scope.window = window;
$scope.alert = alert;
... and then in your template do ng-click="window.alert('hello!')"
...or... ng-click="alert('hello!')"
Or you could do this just once:
$rootScope.window = window;
$rootScope.alert = alert;
Then any scope that prototypically inherits from $rootScope will also have access to window and alert.
... but there are good reasons never to do either of the above, except possibly for debugging purposes. That's not to say that decorating the $rootScope is always a bad idea, there is at least one special case where decorating angular's scope can accomplish something that would be very difficult to do otherwise.
You cannot execute that kind of code in ng directives. They look for content in your local scope.
If you do this in your controller:
$scope.alert = function(){
alert('alerted!');
};
and then in your template
ng-click="alert()"
It will execute the javascript alert properly.
For that to work, you have to define alert on your $scope:
$scope.alert = function(text) {
alert(text);
};
Relevant piece from documentation:
AngularJS restricts access to the Window object from within
expressions since it's a known way to execute arbitrary Javascript
code.
https://docs.angularjs.org/error/$parse/isecwindow
https://docs.angularjs.org/guide/expression
<div class="example2" ng-controller="ExampleController">
Name: <input ng-model="name" type="text"/>
<button ng-click="greet()">Greet</button>
<button ng-click="window.alert('Should not see me')">Won't greet</button>
</div>
I have a simple case:
<div ng-controller="myController">
<tabset>
<tab>
<form name="myForm"></form>
</tab>
</tabset>
</div>
and now, in myController method, I would like to access myForm to call $setPristine:
$scope.myForm.$setPristine()
but I can not. tabset / tab creates isolated/inherited scope. It's just a sample, but I run into this problems when using angular js directives that create isolated scopes many times.
How can I get over this issue? In the past I did something like this (with ng-table that also creates new scope):
ng-init="$parent.saataaTable = this"
but it's far from perfect.
This was one of the most difficult concepts for me to get around and my solution is simple but kind of difficult to explain so bear with me.
Solution 1: Isolate Scopes
When you are only dealing with only isolate scopes (scope: {...}) or no scope (scope: false), you're in luck because the myForm will eventually be there. You just have to watch for it.
$scope.$watch('myForm', function(val) {
if (myForm) {
// now I can call $setPristine
}
});
Solution 2: Child Scopes
This is when you set scope: true or transclude: true. Unless you perform a custom/manual transclusion you will not get myForm on the controller's scope.
The trick is to access the form's controller directly from the form element. This can be done by the following:
// at the form element
element.data('$formController');
// or at the control (input, select, etc.)
element.inheritedData('$formController');
// where 'element' is a jqLite element (form or ng-form)
This sets you up for a new issue: how do we know when and how we can get that element and it's data.
A quick answer is that you need to set up a dummy $watch on your controller's scope to look for (in your case) myForm. When this watch is processed you will then be able to attempt to locate the form. This is necessary due to the fact that typically when your controller first executes the FormController won't yet be on the element's data object.
A quick and simple way to find the form is to simply get all of the forms. NOTE: if there are multiple forms within the element you'll have to add some logic to find the right one. In this case our form is a form element and it's the only one. So, locating it is fairly easy:
// assuming you have inject $element into your controller
$element.find('form').data('$formController');
// where $element is the root element the controller is attached to
// it is injected just like '$scope'
Once you have the controller you can access everything you would normally. It is also important to note that Solution 2 will always work once that FormController is on the element.
I have set up a Plunk to demonstrate the code here, but please note that is a demonstration so not all best practices were kept in mind.
EDIT
I found it important to note that if you don't want to worry about the scopes of the nested directives you can just watch the form name on the scope and handle things there.
$scope.$watch('myForm', function(val) {
if (angular.isDefined(val)) {
// now I have access
} else {
// see if i can `find` the form whose name is 'myForm'
// (this is easy if it is a form element and there's only one)
// then get the FormController for access
}
}
I could not make it work using the answer above, but I found a work-around.
In the form, I created a hidden input field with a ng-model and ng-init that set its value to the form. Then in my submit function in the controller I can access the formController via this ng-model
So, in the HTML, I create a hidden field inside the form:
<input id="test" ng-model="data.myForm" ng-init="data.myForm=myForm" hidden>
And in the Controller I can get hold of the formController via data.myForm
$scope.data.myForm.$setPristine();
It is probably not very good, so I will instead avoid to rely on the $pristine and $dirty properties of the formController and find another way to detect if the form has changed (using a master copy of the object, like they do in the sample in the documentation)