I'm trying to create some custom validations for custom input components.
The directive of the custom input would act as a black box, so I can add the validations that I want from the controller.
To do that I add an attribute for the directive, called 'validations', p.e.:
<custom-input validations="checkEmail() checkIsNotUsed()" type="text" placeholder="Email"></custom-input>
With this example, I can define my validations from the controller as I want, with the idea that when I want to submit from the controller, it will check if this component is validated for 'checkEmail()' and 'checkIsNotUsed()'. To do that, I would need some bidirectional communication between the controller and the inner directive, p.e. using the email:
I've tried different approaches, but I'm not able to get the final goal.
Here is the example I've been working with.
EDIT
I would slightly question whether you need the directive as a complete black box. Firstly, usually you would need some validation messages or some information shown to the user on the different cases of invalid data, so just a true/false return value of any validator isn't enough. Secondly, you're also re-creating a lot of what Angular already gives you with ngForm
, ngModel
, ngModelController
and the input
directive, especially the $setValidity
function of ngModelController
I would put each validator in its own directive that require
s ngModel
: one for email, and one for password for example. You can still pass options in from the overall controller however, but each directive is tailored to the information it needs. For example, a validationEmail
directive can accept an array of existing emails used:
<input validation-email validation-email-used="usedEmails" ng-model="data.email" type="text" placeholder="Email" name="email" ng-required />
Where usedEmails
is an array of emails. The directive could be written as:
app.directive('validationEmail', function() {
return {
require: 'ngModel',
scope: {
validationEmailUsed:'='
},
link: function(scope, element, attributes, ngModelController) {
scope.$watch(function() {
return ngModelController.$viewValue
}, function(email) {
// Test the email and call
// ngModelController.$setValidity(...)
// to set the validity of the email
});
}
};
});
You can see this in action in a modified version of your Plunker
Edit: If you want to integrate with server-side validation, once all client-side validation has passed from the directives, you can do it from function passed to ngSubmit
on the form:
<form name="myForm" ng-submit="submit()">
Which can be coded up using the fact the form exposes itself on the scope, and all its ngModel
controllers (for the same of example, using a $timeout
, rather than calling $http
or a service),
$scope.submit = function() {
$timeout(function() {
$scope.myForm.username.$setValidity('available',false);
$scope.myForm.email.$setValidity('emailFree',false);
},500);
}
An issue with this is that you then might want to mark the input as valid after the user has changed it. You could create a validOnChange
directive, to be used as:
<input ng-model="data.username" type="text" placeholder="Username" name="username" valid-on-change="available" ng-required />
and coded up as:
app.directive('validOnChange', function() {
return {
require: 'ngModel',
scope: '',
link: function(scope, element, attributes, ngModelController) {
scope.$watch(function() {
return ngModelController.$viewValue
}, function() {
ngModelController.$setValidity(attributes.validOnChange,true);
});
}
}
});
You can see this in this Plunker.
Another general benefit of working with ngForm, and ngModelController, is that it adds a lot of classes to the form and element depending on the error/valid state of the inputs, and exposes error state on the scope, so you can show/hide error messages easily using ngIf
, for example.