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