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:
<form name="myForm">
<input type="text" ng-model="fields.one" name="firstField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" />
<br/>
<input type="text" name="secondField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" ng-model="fields.two" />
<br/>
<input type="text" ng-model="fields.three" name="thirdField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" />
<br/>
<button type="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:
<form name="myForm" ...>
<input name="inp1" ng-model="..." required-any="group1" />
<input name="inp2" ng-model="..." required-any="group1" />
<input name="inp3" ng-model="..." required-any="group1" />
<input name="inp4" ng-model="..." required-any="group2" />
<input name="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.
function determineIfRequired(groupName) {
var group = groups[groupName];
if (!group) return false;
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: function postLink(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.
function updateValidity() {
if (group.isRequired) {
modelCtrl.$setValidity('required', false);
} else {
modelCtrl.$setValidity('required', true);
}
}
// Update the group's state and this control's validity.
function validate(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
<input type="text"
ng-model="fields.one"
ng-required="!fields.two" />
<br/>
<input type="text"
ng-model="fields.two"
ng-required="!fields.one" />
If you have three like in question then
<input type="text"
ng-model="fields.one"
ng-required="!(fields.two || fields.three)" />
<br/>
<input type="text"
ng-model="fields.two"
ng-required="!(fields.one || fields.three)" />
<br/>
<input type="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
function updateValidity() {
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: function postLink(scope, elem, attrs, ctrl) {
// If there is no 'ngModel' or no groupName has been specified,
// then there is nothing we can do
if (!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 yourself
if (!scope.__requiredAnyGroups) {
scope.__requiredAnyGroups = {}
}
var groups = scope.__requiredAnyGroups;
// Create a bucket for this group if one does not yet exist
if (!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 value
for (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 value
for (var prop in group) {
if (group.hasOwnProperty(prop) && thisCtrl != group[prop]) {
group[prop].ctrl.$setValidity('requiredAny', oneHasValue);
}
}
// Return the validity of this field
return oneHasValue;
};
}
};
});
Post a Comment for "Form Validation - Required One Of Many In A Group"