6

I am using ng-form as a parent form and child mg-forms for validation with ng-repeat.

<div ng-form="form">
    <div ng-repeat="field in fields">
        <div ng-form="subform"><input...></div>
    </div>
</div>

And validating like this:

if ($scope.form.subform.$invalid && !$scope.form.subform.$error.required) {
    // Do something...
}

(this is very simplified example, I have more different subforms for different input types and with different names, e.g. input[text] is named as a TextForm, input[numeric] is NumericForm etc.)

Everything works as expected if there is only on field. But if ng-repeat generates multiple fields, validation triggers only the last subform, others gets ignored.

Is there a way of cycling through all subforms to check if one of them is invalid?

Also, I am marking all unfilled required fields like this:

if ($scope.form.$error.required) {
     angular.forEach($scope.form.$error.required,
         function (object, index) {
             $scope.form.$error.required[index].$setDirty();
         }
     );
}

So if my fields are done like this:

....ng-form="TextForm" ng-class="{ 'has-error': TextForm.$dirty && TextForm.$invalid }"....

And it marks all the subforms even if there are many of the same ones with the same name.

Maybe I could do something similar with invalid fields? Though tried many things, nothing worked...

12
  • shouldn't ng-form be unique for subforms to actually work? Commented Aug 7, 2014 at 11:00
  • @maurycy do you mean the ng-form name? if it is, then no. They will still work as it is only used as a reference to access validation values for each ng-form context. Commented Aug 7, 2014 at 11:06
  • Well, I don't know that, so that's why I'm asking for a clever solution for this :) I can't put unique names myself as fields are described on the other platform I'm communicating with via API and on every case it might be different, there can be any number of the same field and I need to check if at least one of them (or some) is not valid. Commented Aug 7, 2014 at 11:06
  • @ryeballar but how you can reference i.e. two nested forms with the same name? That's what bothers me Commented Aug 7, 2014 at 11:10
  • Added some example of how I mark required fields. Commented Aug 7, 2014 at 11:16

1 Answer 1

2

A solution for this is to create a directive that assigns the ngModelController's error to a variable in each ng-repeat input.

Below is a possible implementation to get the errors of each subForm. DEMO

JAVASCRIPT (directive)

  .directive('ngModelError', function($parse, $timeout) {
    return {
      require: ['ngModel', 'form'],
      link: function(scope, elem, attr, ctrls) {
        var ngModel = ctrls[0],
            ngForm = ctrls[1],
            fieldGet = $parse(attr.ngModelError),
            fieldSet = fieldGet.assign,
            field = fieldGet(scope);

        $timeout(function() {
          field.$error = ngModel.$error;
          field.ngForm = ngForm;
          fieldSet(scope, field);
        });

      }
    };
  });

HTML

<form name="form" ng-submit="submit(form, fields)" novalidate>
  <div ng-form="subForm" ng-repeat="field in fields"
    ng-class="{'has-error': subForm.$invalid && form.$dirty}">
    <label class="control-label">{{field.label}}</label>
    <input type="{{field.type}}" placeholder="{{field.placeholder}}" 
      ng-model="field.model" name="field" 
      ng-required="field.isRequired" class="form-control" 
      ng-model-error="field" />
  </div>
  <br>
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

JAVASCRIPT (controller)

Please take note how the fields are structure:

  .controller('Ctrl', function($scope) {
    $scope.fields = {
      email: {
        type: 'email',
        placeholder: 'Enter email',
        isRequired: true,
        label: 'Email Address'
      }, 

      password: {
        type: 'password',
        placeholder: 'Enter password',
        isRequired: true,
        label: 'Password'
      }
    };

    $scope.submit = function(form, fields) {
      form.$dirty = true;

      if(form.$valid) {
        // do whatever
      } else {
        // accessing ngForm for email field
        console.log(fields.email.ngForm);
        // accessing errors for email field
        console.log(fields.email.$error);

        // accessing ngForm for password field
        console.log(fields.password.ngForm);
        // accessing errors for password field
        console.log(fields.password.$error);
      }
    };
  })
Sign up to request clarification or add additional context in comments.

1 Comment

A slightly more clear directive can simply have a $watch - scope.$watch(attrs.ngModelError, function(newVal) { newVal.$error = ngModel; }); which has the added benefit of responding to changes on the outer scope.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.