Reputation: 1288
I have created some directives that help me to check input validity in forms that look more or less like this:
app.directive("initialDate", function(){
return{
require: '?ngModel',
restrict:"A",
link:function(scope, element, attrs, ngModel){
scope.$watch(attrs.ngModel, function() {
validate();
});
attrs.$observe('initialDate', function () {
validate();
});
var validate = function() {
var date = Date.parse(ngModel.$viewValue);
var initialDate = Date.parse(attrs.initialDate);
ngModel.$setValidity('initial-date', date >= initialDate);
}
}
}
});
Now, i want to add custom messages to inputs, selects, etc.. that do not pass validation, but I don´t want to make it one by one adding a span or somethinng with ng-if (that´s a lot of work I want to avoid).
My first attempt was to create a directive like this:
app.directive("ngInvalid", function(){
restrict:"C",
...
});
But that didn´t work so now I´m trying this one
app.directive("input", function(){
return {
require:"?ngModel",
link:function(scope, element, attrs, ctrl){
scope.$watch(function() {
return element.attr('class');
}, function(newVal, oldVal){
if ( newVal == oldVal ) return;
if (element.hasClass("ng-invalid")){
element.parent().append("<span class = 'error' style='color:red'>There was an error</span>");
}else{
element.parent().find(".error").remove();
}
});
}
}
});
So, this one works (more or less, it needs more work, hehe), but I will have to create one for selects, textareas, etc.. and make some checks for every type of error if I want to display custom messages.
Besides, it really looks dirty to me. I really think that there must be something that I could do inside the directive that looked more like this:
...
link:function(scope, element, attrs, ctrl){ /***element is the input or select or textarea... ***/
if ( !element.$isValid ){
switch ( element.$validationErrors[0] ){
case "emailValidation": element.parent().append("<span class = 'error' style='color:red'>Email format not correct</span>"); break;
case "initialDate": element.parent().append("<span class = 'error' style='color:red'>Date cannot be previous to {{initialDate}}</span>"); break;
case "pattern": element.parent().append("<span class = 'error' style='color:red'>Format incorrect</span>"); break;
}
}
}
...
I have been looking in the documentation and navigating some questions, but mostly I have found solutions that require to use the form controller, and I want to avoid that.
Is there some methods that would allow me to retrieve the error(s) from an input?
EDIT
To make it clear, let´s say that the inputs are not wrapped inside a form tag, but a div, or something else. How would I access each error inside the input directive?
Upvotes: 0
Views: 556
Reputation: 1288
The previous approach I posted had some issues. The watch over the class was not working as expected, it was not being fired when the class actually changed, but when the next input char was entered. The required error, for example, was appearing when, after being empty, you introduced a new char in the input, and the log in the console and the class in the inspector were different (maybe an angular bug??).
Anyway, I have found what I think is a better solution: Oh, and it´s actually possible to access the validity checks when the control is not wrapped inside a form tag
That´s the code (I didn´t add a blur check yet, but should be easy)
var errMsgDirective = function(){
return{
restrict:"E",
require:"?ngModel",
link:function(scope, element, attrs, ctrl, ngModel){
if ( !attrs.ngModel ) return;
var lang = navigator.language.substr(0,2) || navigator.userLanguage.substr(0,2);
const E_UNKNOWN = 0, E_REQUIRED = 1, E_NAN = 2, E_MIN = 3, E_MAX = 4, E_EMAIL = 5, E_MINLEN = 6, E_MAXLEN = 7, E_PATTERN = 8, E_BLACKLIST = 9, E_EQUAL = 10; E_INITIALDATE = 11;
const ERR_MESSAGES = [
{'es':'Error desconocido', 'en':'Unknown Error'},
{'es':'Este campo es obligatorio', 'en':'This field is mandatory'},
{'es':'Este campo debe ser numérico', 'en':'This field should be numeric'},
{'es':'El valor es inferior al mínimo', 'en':'Value is lower than the minimum'},
{'es':'El valor es superior al máximo', 'en':'Value is higher than the maximum'},
{'es':'El formato de email no es válido', 'en':'Email format incorrect'},
{'es':'No cumple longitud mínima', 'en':'Minimum length not matched'},
{'es':'No cumple longitud máxima', 'en':'Maximum length not matched'},
{'es':'El formato no es válido', 'en':'Format incorrect'},
{'es':'Este valor no está permitido', 'en':'This value is not allowed'},
{'es':'Los campos no coinciden', 'en':'Fields doesn´t match'},
{'es':'La fecha es anterior a la fecha inicial', 'en':'This date is previous to the initial date'},
];
var checkValidity = function(){
if (!ctrl.$touched) return;
var errors = [];
for ( var i in ctrl.$error ){
if ( ctrl.$error[i] == false ) continue;
switch (i){
case "required": errors.push(ERR_MESSAGES[E_REQUIRED][lang]); break;
case "number": errors.push(ERR_MESSAGES[E_NAN][lang]); break;
case "min": errors.push(ERR_MESSAGES[E_MIN][lang]); break;
case "max": errors.push(ERR_MESSAGES[E_MAX][lang]); break;
case "email": errors.push(ERR_MESSAGES[E_EMAIL][lang]); break;
case "minlength": errors.push(ERR_MESSAGES[E_MINLEN][lang]); break;
case "maxlength": errors.push(ERR_MESSAGES[E_MAXLEN][lang]); break;
case "pattern": errors.push(ERR_MESSAGES[E_PATTERN][lang]); break;
case "blacklist": errors.push(ERR_MESSAGES[E_BLACKLIST][lang]); break;
case "equals": errors.push(ERR_MESSAGES[E_EQUAL][lang]); break;
case "initial-date": errors.push(ERR_MESSAGES[E_INITIALDATE][lang]); break;
default: errors.push(ERR_MESSAGES[E_UNKNOWN][lang]); break;
}
}
element.parent().find(".error").remove();
if ( errors.length == 0){
}else{
$errMessage = errors.join("; ");
element.parent().append("<span class = 'error'>"+$errMessage+"</span>");
}
}
scope.$watch(function(){
return JSON.stringify(ctrl.$error);
}, function(){
checkValidity();
});
scope.$watch(function(){
return ctrl.$touched;
}, function(){
checkValidity();
})
}
}
}
app.directive("input", errMsgDirective);
app.directive("textarea", errMsgDirective);
app.directive("select", errMsgDirective);
There are some things I want to point:
1st: I can access all errors relative to each control inside the directive with ctrl.$error (for god´s sake, why there wasn´t a single answer in all stack overflow saying you can access control errors with .$error ?? ¬¬)
2nd: I had a real hard time putting a watch that worked: I tried all of the following:
scope.$watch(attrs.ngModel, function(){...}) //This one did not work because the model doesn´t change when the validity checks are not met
scope.$watch(ctrl.$valid, function(){...}) //Never fired
scope.$watch(ctrl.$invalid, function(){...}) //Never fired
scope.$watch(ctrl.$error, function(){...}) //Never fired
scope.$watch(function(){ return ctrl.$error }, function(){...}) //Never fired
scope.$watch(function(){ return JSON.stringify(ctrl.$error) }, function(){...}) //BINGO!!!
Here you can see it working:
http://codepen.io/sergio0983/pen/ZOvPvW?editors=1011
Upvotes: 1
Reputation: 1288
Short Answer
No, it´s not possible to access to validity checks without the form controller.
Workaround
I used input directive to achieve what I wanted, but it doesn´t seems too elegant to me ( I´d really like to use a directive that affects textareas and selects without having to add my-custom-directive to every field I want to validate )
(function(){
var lang = navigator.language.substr(0,2) || navigator.userLanguage.substr(0,2);
const E_UNKNOWN = 0,
E_REQUIRED = 1,
E_NAN = 2,
E_MIN = 3,
E_MAX = 4,
E_EMAIL = 5,
E_MINLEN = 6,
E_MAXLEN = 7,
E_PATTERN = 8,
E_BLACKLIST = 9,
E_EQUAL = 10;
E_INITIALDATE = 11;
const ERR_MESSAGES = [
{'es':'Error desconocido', 'en':'Unknown Error'},
{'es':'Este campo es obligatorio', 'en':'This field is mandatory'},
{'es':'Este campo debe ser numérico', 'en':'This field should be numeric'},
{'es':'El valor es inferior al mínimo', 'en':'Value is lower than the minimum'},
{'es':'El valor es superior al máximo', 'en':'Value is higher than the maximum'},
{'es':'El formato de email no es válido', 'en':'Email format incorrect'},
{'es':'No cumple longitud mínima', 'en':'Minimum length not matched'},
{'es':'No cumple longitud máxima', 'en':'Maximum length not matched'},
{'es':'El formato no es válido', 'en':'Format incorrect'},
{'es':'Este valor no está permitido', 'en':'This value is not allowed'},
{'es':'Los campos no coinciden', 'en':'Fields doesn´t match'},
{'es':'La fecha es anterior a la fecha inicial', 'en':'This date is previous to the initial date'},
];
var lang = navigator.language.substr(0,2) || navigator.userLanguage.substr(0,2);
app.directive("input", function(){
return {
scope:{},
link:function(scope, element, attrs, ctrl, ngModel){
scope.blurred = false;
element[0].addEventListener("blur", function(){
scope.blurred = true;
element.parent().find(".error").remove();
checkValidity();
});
var checkValidity = function(){
var classList = element.attr('class');
if (!classList) return;
var matches = classList.match(/ng-invalid-(.*?)(\s|$)/);
if ( matches == null ) return;
if ( !(1 in matches) ) return;
var $err = matches[1];
var $errMessage = "";
switch($err){
case "required": $errMessage = ERR_MESSAGES[E_REQUIRED][lang]; break;
case "number": $errMessage = ERR_MESSAGES[E_NAN][lang]; break;
case "min": $errMessage = ERR_MESSAGES[E_MIN][lang]; break;
case "max": $errMessage = ERR_MESSAGES[E_MAX][lang]; break;
case "email": $errMessage = ERR_MESSAGES[E_EMAIL][lang]; break;
case "minlength": $errMessage = ERR_MESSAGES[E_MINLEN][lang]; break;
case "maxlength": $errMessage = ERR_MESSAGES[E_MAXLEN][lang]; break;
case "pattern": $errMessage = ERR_MESSAGES[E_PATTERN][lang]; break;
case "blacklist": $errMessage = ERR_MESSAGES[E_BLACKLIST][lang]; break;
case "equals": $errMessage = ERR_MESSAGES[E_EQUAL][lang]; break;
case "initial-date": $errMessage = ERR_MESSAGES[E_INITIALDATE][lang]; break;
default: $errMessage = ERR_MESSAGES[E_UNKNOWN][lang]; break;
}
if (element.hasClass("ng-invalid")){
element.parent().append("<span class = 'error'>"+$errMessage+"</span>");
}
}
}
}
});
})();
Here is a link to a working codepen: http://codepen.io/sergio0983/pen/AXxNpN?editors=1111
For some reason, in codepen the error doesn´t get removed if I don´t use the $ selector over the angular element ()
Upvotes: 0