How to validate inputs dynamically created using ng-repeat, ng-show (angular) How to validate inputs dynamically created using ng-repeat, ng-show (angular) javascript javascript

How to validate inputs dynamically created using ng-repeat, ng-show (angular)


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>

Demo

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>


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/


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-6832095https://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;            }          }        }      }    };});