How to validate inputs dynamically created using ng-repeat, ng-show (angular)
Solution 1
AngularJS relies on input names to expose validation errors.
Unfortunately, as of today, it is not possible (without using a custom directive) to dynamically generate a name of an input. Indeed, checking input docs we can see that the name attribute accepts a string only.
To solve the 'dynamic name' problem you need to create an inner form (see ng-form):
<div ng-repeat="social in formData.socials">
<ng-form name="urlForm">
<input type="url" name="socialUrl" ng-model="social.url">
<span class="alert error" ng-show="urlForm.socialUrl.$error.url">URL error</span>
</ng-form>
</div>
The other alternative would be to write a custom directive for this.
Here is the jsFiddle showing the usage of the ngForm: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/
Solution 2
Since the question was asked the Angular team has solved this issue by making it possible to dynamically create input names.
With Angular version 1.3 and later you can now do this:
<form name="vm.myForm" novalidate>
<div ng-repeat="p in vm.persons">
<input type="text" name="person_{{$index}}" ng-model="p" required>
<span ng-show="vm.myForm['person_' + $index].$invalid">Enter a name</span>
</div>
</form>
Angular 1.3 also introduced ngMessages, a more powerful tool for form validation. You can use the same technique with ngMessages:
<form name="vm.myFormNgMsg" novalidate>
<div ng-repeat="p in vm.persons">
<input type="text" name="person_{{$index}}" ng-model="p" required>
<span ng-messages="vm.myFormNgMsg['person_' + $index].$error">
<span ng-message="required">Enter a name</span>
</span>
</div>
</form>
Solution 3
If you don't want to use ng-form you can use a custom directive that will change the form's name attribute. Place this directive as an attribute on the same element as your ng-model.
If you're using other directives in conjunction, be careful that they don't have the "terminal" property set otherwise this function won't be able to run (given that it has a priority of -1).
For example, when using this directive with ng-options, you must run this one line monkeypatch: https://github.com/AlJohri/bower-angular/commit/eb17a967b7973eb7fc1124b024aa8b3ca540a155
angular.module('app').directive('fieldNameHack', function() {
return {
restrict: 'A',
priority: -1,
require: ['ngModel'],
// the ngModelDirective has a priority of 0.
// priority is run in reverse order for postLink functions.
link: function (scope, iElement, iAttrs, ctrls) {
var name = iElement[0].name;
name = name.replace(/\{\{\$index\}\}/g, scope.$index);
var modelCtrl = ctrls[0];
modelCtrl.$name = name;
}
};
});
I often find it useful to use ng-init to set the $index to a variable name. For example:
<fieldset class='inputs' ng-repeat="question questions" ng-init="qIndex = $index">
This changes your regular expression to:
name = name.replace(/\{\{qIndex\}\}/g, scope.qIndex);
If you have multiple nested ng-repeats, you can now use these variable names instead of $parent.$index.
Definition of "terminal" and "priority" for directives: https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object
Github Comment regarding need for ng-option monkeypatch: https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095 https://twitter.com/aljohri/status/482963541520314369
UPDATE:
You can also make this work with ng-form.
angular.module('app').directive('formNameHack', function() {
return {
restrict: 'A',
priority: 0,
require: ['form'],
compile: function() {
return {
pre: function(scope, iElement, iAttrs, ctrls) {
var parentForm = $(iElement).parent().controller('form');
if (parentForm) {
var formCtrl = ctrls[0];
delete parentForm[formCtrl.$name];
formCtrl.$name = formCtrl.$name.replace(/\{\{\$index\}\}/g, scope.$index);
parentForm[formCtrl.$name] = formCtrl;
}
}
}
}
};
});
Solution 4
Use the ng-form directive inside of the tag in which you are using the ng-repeat directive. You can then use the scope created by the ng-form directive to reference a generic name. For example:
<div class="form-group col-sm-6" data-ng-form="subForm" data-ng-repeat="field in justificationInfo.justifications"">
<label for="{{field.label}}"><h3>{{field.label}}</h3></label>
<i class="icon-valid" data-ng-show="subForm.input.$dirty && subForm.input.$valid"></i>
<i class="icon-invalid" data-ng-show="subForm.input.$dirty && subForm.input.$invalid"></i>
<textarea placeholder="{{field.placeholder}}" class="form-control" id="{{field.label}}" name="input" type="text" rows="3" data-ng-model="field.value" required>{{field.value}}</textarea>
</div>
Credit to: http://www.benlesh.com/2013/03/angular-js-validating-form-elements-in.html
Solution 5
Added more complex example with "custom validation" on the side of controller http://jsfiddle.net/82PX4/3/
<div class='line' ng-repeat='line in ranges' ng-form='lineForm'>
low: <input type='text'
name='low'
ng-pattern='/^\d+$/'
ng-change="lowChanged(this, $index)" ng-model='line.low' />
up: <input type='text'
name='up'
ng-pattern='/^\d+$/'
ng-change="upChanged(this, $index)"
ng-model='line.up' />
<a href ng-if='!$first' ng-click='removeRange($index)'>Delete</a>
<div class='error' ng-show='lineForm.$error.pattern'>
Must be a number.
</div>
<div class='error' ng-show='lineForm.$error.range'>
Low must be less the Up.
</div>
</div>
Related videos on Youtube
Comments
-
PFranchise almost 2 years
I have a table that is created using ng-repeat. I want to add validation to each element in the table. The problem is that each input cell has the same name as the cell above and below it. I attempted to use the
{{$index}}
value to name the inputs, but despite the string literals in HTML appearing correct, it is now working.Here is my code as of now:
<tr ng-repeat="r in model.BSM "> <td> <input ng-model="r.QTY" class="span1" name="QTY{{$index}}" ng-pattern="/^[\d]*\.?[\d]*$/" required/> <span class="alert-error" ng-show="form.QTY{{$index}}.$error.pattern"><strong>Requires a number.</strong></span> <span class="alert-error" ng-show="form.QTY{{$index}}.$error.required"><strong>*Required</strong></span> </td> </tr>
I have tried removing the
{{}}
from index, but that does not work either. As of now, the validation property of the input is working correctly, but the error message is not displayed.Anyone have any suggestions?
Edit: In addition to the great answers below, here is a blog article that covers this issue in more detail: http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2/
-
Will Strohl over 8 yearsFor those reading this in 2015... the top voted answer is NOT the correct one any longer. Look lower. :)
-
osiris over 8 yearsThis seems the be the "for 2015" answer @WillStrohl talks about.
-
PFranchise over 8 yearsWhat is proper SO etiquette here? Should I leave the accepted answer since it was correct at the time or accept the correct answer for today? Just want this seemingly popular thread to helpful to new visitors.
-
osiris over 6 years@PFranchise, I don't know but i think a visible note about it could help. Maybe as an edit to your question, so the note stays where more people can see it.
-
-
PFranchise over 11 yearsThanks for the reply. I am still having an issue after using forms and was wondering if you knew off hand if using tables r ng-patter validation might cause this solution to break.
-
pkozlowski.opensource over 11 yearsAngularJS uses DOM traversal to compile its templates so your HTML must be a valid one. If you could prepare a jsFiddle with your template it would be easier to help.
-
PFranchise over 11 yearsI figured out the issue. Was simple after looking over your links. Thanks a ton for quickly providing a solution that i will be using often!
-
Felipe Castro over 11 yearsGreat solution! Didn't realize at first glance that the key is in how ng-form directive allows you to have nestable forms. Thanks!
-
Ian Warburton almost 11 yearsThat's great. But is it valid html to have multiple text boxes with the same name?
-
Blowsie over 10 yearsAs far as I know its not possible to use a dynamic name even with a custom directive, as I just found out after 2 hours of hacking
-
Blowsie over 10 years@IanWarburton its valid but it sucks when you have different fields using the same name as it drowns the browsers user input history
-
rasmusvhansen over 10 yearsAlso there seems to be an issue when having radio buttons inside the nested repeated ng-form. See my question here: stackoverflow.com/questions/19114478/…
-
Blowsie over 10 yearsNesting forms isnt considered to be valid HTML stackoverflow.com/questions/379610/can-you-nest-html-forms Is angular planning a fix for this?
-
pkozlowski.opensource over 10 years@Blowsie you are not nesting real form here, but rather
ng-form
DOM elements, so the link to the other SO question is not relevant here. -
Blowsie over 10 yearsIm aiming to keep polyfills / additional fixes for IE8 to a mininum, so I am using the
data-ng-
approach throughout my entire application. Is it possible to achieve a similar results without using<ng-form>
? -
Blowsie over 10 years@pkozlowski.opensource this issue is over a year old now, I was wondering if you know of a github thread where I can track it?
-
ivkremer about 10 yearsGreat. It should be noticed that if your
ng-repeat
is bound ontable tr
then you have to useng-form="myname"
attr. -
Marc M. about 10 yearsThis solution is awesome cause it solves the problem in an incredibly AngularJS way. Create a new scope! You keep the name the same and have a different scope for each repeat. That's what the ng-form is doing and this is not a bug that needs to be fixed, but rather the nature of AngularJS.
-
PFranchise almost 10 yearsJust to make it clear, this answer not being selected, is not indicative of it not being the best answer. It was just posted almost 2 years after the question was originally asked. I would consider both this answer and tomGreen's in addition to the selected answer if you run into this same issue.
-
Jesper Tejlgaard about 9 yearsThe accepted answer did not work for me. This one however did. (I use Angular 2.1.14)
-
tanguy_k about 9 yearsThis answer should be edited: the issue github.com/angular/angular.js/issues/1404 has been solved since AngularJS 1.3.0 (commit from september 2014)
-
ABCD.ca over 8 yearsIf you're interested in this working with ngMessages see my answer (which builds on this great answer)
-
Abdellah Alaoui over 8 years+1 this answer worked for me check the link: you just need to add
ng-form="formName"
to the tag that has ng-repeat ... it worked like a charm :) -
PavanSandeep over 8 yearsSo, lets say we add pagination to this ng-repeat, then the controls on the second/nth page would not get validated. How do we solve for that ?
-
dinkydani almost 8 yearsThis is perfect and much easier than doing a directive - can pass a form into components and use this method. Thanks mate!
-
Patrick Szalapski almost 8 yearsI noticed that your form name can't have hyphens if you want this to work. Anyone know why this is?
-
HoffZ almost 8 years@PatrickSzalapski: it's because the form name is used by Angular and variable names with hyphens is not valid syntax in Javascript. Workaround: <span ng-show="vm['my-form']['person_' + $index].$invalid">Enter a name</span>
-
Ashit Vora almost 8 yearsIs it possible to validate fields of other forms inside ng-repeat? I've a situation where I want value of all dynamically create fields be different. So if the value of any field is duplicate, it will show an error in only one field and not both. So I wish to revalidate all the fields. How can this be achieved?
-
jonathanwiesel over 7 yearsI noticed that if you remove a repeated item dynamically, the
$valid
property for the input gets incorrectlyfalse
-
RGS over 7 yearsIs it possible to validate textbox by using id attribute instead of name attribute using ng-messages?
-
codingbbq over 6 yearswhat is you want all your errors to display at one place say at the top of the form?
-
NeverGiveUp161 about 6 years@HoffZ : can you please take a look at my case? am using ng-options Please help