AngularJS multiple forms with ng-repeat validation
Asked Answered
A

1

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...

Amabel answered 7/8, 2014 at 10:55 Comment(12)
shouldn't ng-form be unique for subforms to actually work?Spearhead
@Spearhead 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.Lyndalynde
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.Amabel
@Lyndalynde but how you can reference i.e. two nested forms with the same name? That's what bothers meSpearhead
Added some example of how I mark required fields.Amabel
@maurycy, hmm I don't think there is a need to access these subForms in a controller, simply use them in a declarative way, can you provide a use case wherein it maybe relevant. @Julius, Can you tell me your use case, why are you iterating through all ngForms perhaps there is another way to solve your problem.Lyndalynde
There are different ways to overcome this situation. First, use ng-disabled directive to disable the submit button when form.$invalid is true. Second, pass form as a parameter for your ng-submit callback, ng-submit(form) and then in your controller you could do, if(form.$invalid) {/*your code */}Lyndalynde
@Lyndalynde I can do that but I need to display different messages, e.g. Text field is not valid, Numeric field is not valid etc. It's a requirement. And at first I had problems with a single parent form (without subforms) together with ng-repeat. So after a while of searching for an answer, parent-child forms were offered which works perfectly (almost) just struggling with this part. At first I didn't even notice, it was showing me correct error messages when using one of each inputs but when I tested with multiple same inputs, I encountered this problem...Amabel
I noticed that you're using bootstrap css for showing errors, do you plan to put the errors inside a .form-group below the input elements as .help-blocks? If that is the case then simply use ng-show in each of those .help-blocks within the subForm's context.Lyndalynde
@Lyndalynde I am using alerts which are shown on top of the page and wanted to display error messages according to the problem... But I guess I'll stick to showing that "something's wrong" message not pointing out the exact problem...Amabel
I have posted an answer, check if it complies with your use case.Lyndalynde
@Spearhead shouldn't `<div ng-form="subform" + field.$index><input...></div> or similar syntax work in order to create all his separate unique id's for his forms within a repeater?Intransigent
L
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);
      }
    };
  })
Lyndalynde answered 7/8, 2014 at 13:15 Comment(1)
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.Christianly

© 2022 - 2024 — McMap. All rights reserved.