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

Julius
  • 2,473
  • 3
  • 20
  • 26
  • shouldn't ng-form be unique for subforms to actually work? – maurycy Aug 07 '14 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. – ryeballar Aug 07 '14 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. – Julius Aug 07 '14 at 11:06
  • @ryeballar but how you can reference i.e. two nested forms with the same name? That's what bothers me – maurycy Aug 07 '14 at 11:10
  • Added some example of how I mark required fields. – Julius Aug 07 '14 at 11:16
  • @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. – ryeballar Aug 07 '14 at 11:46
  • 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 */}` – ryeballar Aug 07 '14 at 11:55
  • @ryeballar 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... – Julius Aug 07 '14 at 12:05
  • 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-block`s? If that is the case then simply use `ng-show` in each of those `.help-block`s within the `subForm`'s context. – ryeballar Aug 07 '14 at 12:07
  • @ryeballar 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... – Julius Aug 07 '14 at 12:17
  • I have posted an answer, check if it complies with your use case. – ryeballar Aug 07 '14 at 13:23
  • @maurycy shouldn't `
    or similar syntax work in order to create all his separate unique id's for his forms within a repeater?
    – Pakk Jul 13 '15 at 21:18

1 Answers1

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);
      }
    };
  })
ryeballar
  • 29,658
  • 10
  • 65
  • 74
  • 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. – jkjustjoshing Sep 24 '14 at 19:11