Form Validation - Required One Of Many In A Group
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.
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"