I wanted to create directive in angular that would display error message if entered value is not in valid format. What I finally came with is:
http://plnkr.co/edit/l2CWu8u6sMtSj3l0kdvd?p=preview
app.directive('kbDecimalValidation', function ($parse, $rootScope, $compile) {
return {
restrict: 'E',
scope: {
inputFieldRef: '=?',
kbModel: '=ngModel',
kbRequired: '@required',
inputName: '@'
},
template: '<span ng-form="kbDecimalValidationForm">' +
'<input ng-model="kbModel" ng-required="kbRequired" ' +
'size="6"' +
'ng-pattern="/^[0-9]+(\\.[0-9][0-9]?)?$/" ' +
'/>' +
'<div ng-show="!kbDecimalValidationForm[inputName].$valid && kbDecimalValidationForm[inputName].$error.required"' +
'style="color: red; font-weight: bold">Field is required</div>' +
'<div ng-show="!kbDecimalValidationForm[inputName].$valid && kbDecimalValidationForm[inputName].$error.pattern"' +
'style="color: red; font-weight: bold">Bad format format,<br />allowed: "0.00"' +
'</div>' +
'</span>',
replace: true,
priority: 50,
controller: function($scope){
$scope.$watch(
'kbDecimalValidationForm[inputName]',
function (value) {
$scope.inputFieldRef = value;
});
},
compile: function (tElement, tAttrs, transclude) {
if($.tempKbDecimalValidationGUID == undefined){
$.tempKbDecimalValidationGUID = 0;
}
var guidInputName = 'XXX' + ++$.tempKbDecimalValidationGUID + 'XXX';
$(tElement).find('input').attr('name', guidInputName); //it is here to force angular to assign value to: $scope.kbDecimalValidationForm[guidInputName]
//there is no expression in name, so angular won't add it to $$watchers
return {
pre: function preLink($scope, iElement, iAttrs, controller) {
//$(iElement).find('input').attr('name', iAttrs.inputName); //it doesn't work if there is expression in inputName,
// expression will be evaluated later (after linkFunction)
// and the name assigned here will be updated (re-parsed by angular watch)
},
post: function postLink($scope, iElement, iAttrs, controller) {
$scope.kbDecimalValidationForm[iAttrs.inputName] = $scope.kbDecimalValidationForm[guidInputName]; //rewrite value to make it available by parsed name
$(iElement).find('input').attr('name', iAttrs.inputName); //assign parsed name - GUID didn't contain expression, so it is not in $$watchers,
// so it won't be replaced by angular
}
}
}
};
});
but I'm sure it is not propper way to do it. I expirience a lot of problems with it. Can somebody tell me what is the propper way to achieve it?
PS: The problem I'm facing right now with the above directive is: when I use it in ng-repeat, and reorder repeated source the directive does not work correctly. I suspect the problem is with my "hacking coding" (the tempKbDecimalValidationGUID, and $scope.kbDecimalValidationForm variables)
For Angular 1.2.x, you will have to use the ngModel.$parsers
and $formatters
pipelines for validation. Angular 1.3 has the dedicated $validators
and even $asyncValidators
pipelines. So the outline of a validation solution for 1.2.x would be:
.directive("kbDecimalValidation", function() {
function parseDecimal(value) {
// implement the conversion from a string to number, e.g. (simpistic):
var val = parseFloat(value);
// return a number (for success), null (for empty input), or a string (describing the error on error)
}
function formatDecimal(value) {
// format a number to a string that will be displayed; the inverse of parseDecimal()
// throw error if something goes wrong
}
return {
restrict: "A",
require: "ngModel",
link: function(scope, elem, attrs, ngModel) {
ngModel.$parsers.push(function(value) {
var val = parseDecimal(value);
if( typeof val === "string" ) {
// an error occured
ngModel.$setValidity("kbDecimal", false);
// return undefined!
}
else {
ngModel.$setValidity("kbDecimal", true);
return val;
}
});
ngModel.$formaters.push(function(value) {
if( value == null || typeof value === "number" ) {
ngModel.$setValidity("kbDecimal", true);
try {
return formatDecimal(value);
}
catch(e) {
ngModel.$setValidity("kbDecimal", false);
return "";
}
}
else {
ngModel.$setValidity("kbDecimal", false);
return "";
}
});
}
};
})
Many details will need work, but hopefully you get the idea. The parseDecimal()
/ formatDecimal()
functions could even go to a dedicated Angular service, if they become too complex, or need to be reusable.
A quick and dirty way is to use DOM manipulation through the elem
argument of link()
. Eg:
link: function(scope, elem, attrs, ngModel) {
...
scope.$watch(
function() { return ngModel.$error.kbDecimal; },
function(newval) {
var container = elem.parent();
// append or remove the message as necessary
...
}
);
}
Another way, less quick but more componentized is to make 2 more directives. One will be placed on the <span ng-form>
element (the container), another will display the messages. The HTML would be like:
<span ng-form="..." validation-container>
<input ... kb-decimal-validation />
<validation-messages></validation-messages>
</span>
Both kbDecimalValidation
and validationMessages
will require the validationContainer
; the controller of the validationContainer
will have a method, called by the kbDecimalValidation
, to get notified about the $error
object. It will also expose a copy of the $error
object. The validationMessages
will $watch
that object and display or hide the appropriate messages.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.