I have some custom validation code, which includes a $formatter. (I store currency in pence for correctness, but display in pounds.pence.)
If the user types '10' into the input (which is a valid value), the input remains displaying '10' after they move to the next field.
I would like it to display 10.00 for consistency.
If the model changed the value to 1000, then the formatter would make the field display '10.00'.
I would like the formatter to run on field.blur() (so long as the input is valid).
My problem is that if I change the model value from 10 to 10, there is understandably no change, and so the field is not re-rendered.
code:
var CURRENCY_REGEXP = /^\-?\d+(\.?\d?\d?)?$/;
app.directive('currency', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
if (CURRENCY_REGEXP.test(viewValue)) {
// it is valid
ctrl.$setValidity('currency', true);
console.log("valid");
return viewValue * 100;
} else if (viewValue === '') {
return 0;
} else {
// it is invalid, return undefined (no model update)
ctrl.$setValidity('currency', false);
console.log("invalid");
return undefined;
}
});
ctrl.$formatters.push(function(modelValue) {
if (modelValue === 0) { // we're using integer pence, so this is safe
return '';
}
return (modelValue / 100).toFixed(2);
});
}
};
});
P.S. This has nothing to do with Angular's built-in 'currency'.
Update: I've added a 'renderOnBlur' directive, as per Andy's answer. It gets called, but calling the render method does not re-render the input. i.e. '10' stays as '10', rather than changing to '10.00' as desired.
(When the model value changes in these fields, they are correctly rendered with the 2 decimal places.)
The page which Andy mentions http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController says that you have to implement $render
yourself. This seems odd, as the inputs are already rendered correctly when the model value changes.
app.directive('renderOnBlur', function() {
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, elm, attrs, ctrl) {
elm.bind('blur', function() {
console.log('rendering ctrl', ctrl);
ctrl.$render();
});
}
};
});
P.S. I have no idea what restrict: 'A',
does - it's true cargo-cult programming at its worst. The require: 'ngModel',
seems necessary to populate the ctrl
parameter.
Inspired by the answer from @Dan Doyen, I rewrote it as:
app.directive('renderOnBlur', function() {
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, elm, attrs, ctrl) {
elm.bind('blur', function() {
var viewValue = ctrl.$modelValue;
for (var i in ctrl.$formatters) {
viewValue = ctrl.$formatters[i](viewValue);
}
ctrl.$viewValue = viewValue;
ctrl.$render();
});
}
};
});
This has the benefit of being generic for any $formatter, rather than repeating the formatter code as in Dan's answer.
$formatters
, the Angular documentation says "The functions are called in reverse array order, each passing the value through to the next." The directive calls them in the wrong order. Just thought I'd note this for anyone else, as this bit me when working with multiple formatters. – Dark Falcon