Skip to content Skip to sidebar Skip to footer

Form Validation - Required One Of Many In A Group

In the project I'm working on at the moment I currently have three textboxes and I need to validate that at least one of the text boxes has been populated. I've been reading into c

Solution 1:

You can use ng-required to force the user to fill at least one field by checkingthe length attribute of the string.

You can do the following for example:

<formname="myForm"><inputtype="text"ng-model="fields.one"name="firstField"ng-required="!(fields.one.length || fields.two.length || fields.three.length)" /><br/><inputtype="text"name="secondField"ng-required="!(fields.one.length || fields.two.length || fields.three.length)"ng-model="fields.two" /><br/><inputtype="text"ng-model="fields.three"name="thirdField"ng-required="!(fields.one.length || fields.two.length || fields.three.length)" /><br/><buttontype="submit"ng-disabled="!myForm.$valid">Submit</button></form>

See this working fiddle example for more details.

You can have more details about required vs ng-required by reading this question

Solution 2:

There are several approaches and the best option depends on your exact requirements.

Here is one approach that I found to be generic enough and flexible. By "generic" I mean it doesn't only work for text-fields, but also for other kinds of inputs, such as check-boxes. It's "flexible" because it allows any number of control-groups, such that at least one control of each group must be non-empty. Additionally, there is no "spacial" constraint - the controls of each group can be anywhere inside the DOM (if required, it is easy to constrain them inside a single form).

The approach is based on defining a custom directive (requiredAny), similar to ngRequired, but taking into account the other controls in the same group. Once defined, the directive can be used like this:

<formname="myForm"...><inputname="inp1"ng-model="..."required-any="group1" /><inputname="inp2"ng-model="..."required-any="group1" /><inputname="inp3"ng-model="..."required-any="group1" /><inputname="inp4"ng-model="..."required-any="group2" /><inputname="inp5"ng-model="..."required-any="group2" /></form>

In the above example, at least one of [inp1, inp2, inp3] must be non-empty, because they belong to group1. The same holds for [inp4, inp5], which belong to group2.


The directive looks like this:

app.directive('requiredAny', function () {
  // Map for holding the state of each group.var groups = {};

  // Helper function: Determines if at least one control//                  in the group is non-empty.functiondetermineIfRequired(groupName) {
    var group = groups[groupName];
    if (!group) returnfalse;

    var keys = Object.keys(group);
    return keys.every(function (key) {
      return (key === 'isRequired') || !group[key];
    });
  }

  return {
    restrict: 'A',
    require: '?ngModel',
    scope: {},   // An isolate scope is used for easier/cleaner// $watching and cleanup (on destruction).
    link: functionpostLink(scope, elem, attrs, modelCtrl) {
      // If there is no `ngModel` or no groupName has been specified,// then there is nothing we can do.if (!modelCtrl || !attrs.requiredAny) return;

      // Get a hold on the group's state object.// (If it doesn't exist, initialize it first.)var groupName = attrs.requiredAny;
      if (groups[groupName] === undefined) {
        groups[groupName] = {isRequired: true};
      }

      var group = scope.group = groups[groupName];

      // Clean up when the element is removed.
      scope.$on('$destroy', function () {
        delete(group[scope.$id]);
        if (Object.keys(group).length <= 1) {
          delete(groups[groupName]);
        }
      });

      // Update the validity state for the 'required' error-key// based on the group's status.functionupdateValidity() {
        if (group.isRequired) {
          modelCtrl.$setValidity('required', false);
        } else {
          modelCtrl.$setValidity('required', true);
        }
      }

      // Update the group's state and this control's validity.functionvalidate(value) {
        group[scope.$id] = !modelCtrl.$isEmpty(value);
        group.isRequired = determineIfRequired(groupName);
        updateValidity();
        return group.isRequired ? undefined : value;
      }

      // Make sure re-validation takes place whenever://   either the control's value changes//   or the group's `isRequired` property changes
      modelCtrl.$formatters.push(validate);
      modelCtrl.$parsers.unshift(validate);
      scope.$watch('group.isRequired', updateValidity);
    }
  };
});

This might not be so short, but once included into a module, it is very easy to integrate into your forms.


See, also, this (not so) short demo.

Solution 3:

It's too late but might be can save some one's time:

If there are only two fields, and want to make one of them required then

<inputtype="text" 
      ng-model="fields.one" 
      ng-required="!fields.two" />
<br/>
<inputtype="text" 
      ng-model="fields.two"
      ng-required="!fields.one"  />

If you have three like in question then

<inputtype="text" 
      ng-model="fields.one" 
      ng-required="!(fields.two || fields.three)" />
<br/>
<inputtype="text" 
      ng-model="fields.two"
      ng-required="!(fields.one || fields.three)"  />
<br/>
<inputtype="text" 
      ng-model="fields.three" 
      ng-required="!(fields.one|| fields.two)" />

If more than this, I will suggest to write a function on scope and watch it.

See the working example

Solution 4:

modification to ExpertSystem's answer (https://stackoverflow.com/a/24230876/4968547) so that his code works in the latest angularjs.

i changed the updateValidity() to set parse also to true/false

functionupdateValidity() {
            if (group.isRequired) {
                modelCtrl.$setValidity('required', false);
                modelCtrl.$setValidity('parse', false); 
            } else {
                modelCtrl.$setValidity('required', true);
                modelCtrl.$setValidity('parse', true);
            }
        }

now its working fine for me

Solution 5:

Ran into this same problem last week; ExpertSystem's solution was a good start, but I was looking for a few enhancements to it:

  • Use Angular 1.4.3
  • Use ngMessages

I eventually wound up with this example on JSFiddle - hope that helps inspire others in the same boat! Relevant JS code from the Fiddle:

var app = angular.module('myApp', ['ngMessages']);
app.controller('myCtrl', function ($scope) {
    $scope.sendMessage = function () {
        $scope.myForm.$submitted = true;

        if ($scope.myForm.$valid) {
            alert('Message sent !');
        }
    };
});

app.directive('requiredAny', function () {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: functionpostLink(scope, elem, attrs, ctrl) {
            // If there is no 'ngModel' or no groupName has been specified,// then there is nothing we can doif (!ctrl || !attrs.requiredAny) { return };

            // If this is the first time we've used this directive in this scope,// create a section for it's data. If you need / want to make use of// an isolate scope you'll need to make 'var groups' scoped to the directive;// but then you may want to look in to clearing out group entries yourselfif (!scope.__requiredAnyGroups) {
                scope.__requiredAnyGroups = {}
            }
            var groups = scope.__requiredAnyGroups;

            // Create a bucket for this group if one does not yet existif (!groups[attrs.requiredAny]) {
                groups[attrs.requiredAny] = {};
            }
            var group = groups[attrs.requiredAny];

            // Create the entry for this control
            group[attrs.ngModel] = {
                ctrl: ctrl,
                hasValue: false
            };

            ctrl.$validators.requiredAny = function(view, value) {
                var thisCtrl = group[attrs.ngModel],
                        ctrlValue = (typeof value !== 'undefined') && value,
                        oneHasValue = false;

                thisCtrl.hasValue = ctrlValue;

                // First determine if any field in the group has a valuefor (var prop in group) {
                    if (group.hasOwnProperty(prop) && group[prop].hasValue) {
                        oneHasValue = true;
                        break;
                    }
                }

                // Set the validity of all other fields based on whether the group has a valuefor (var prop in group) {
                    if (group.hasOwnProperty(prop) && thisCtrl != group[prop]) {
                        group[prop].ctrl.$setValidity('requiredAny', oneHasValue);
                    }
                }

                // Return the validity of this fieldreturn oneHasValue;
            };
        }
    };
});

Post a Comment for "Form Validation - Required One Of Many In A Group"