Jason Jason - 3 months ago 14
AngularJS Question

Angular phone number directive allows letters after double tapping

I have a directive that only allows numbers and formatting. It puts the formatting in the appropriate positions i.e. 123-123-1234

The problem is when you type a letter the first time it will not show but if you type the SAME letter a second time it will show i.e. 12r

here is the directive code:

app.directive('phoneNumber', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, el, atts, ngModel) {

/* called when model is changed from the input element */
ngModel.$parsers.unshift(function(viewValue) {

var numbers = viewValue.replace(/\D/g, ''),
char = {3:'-',6:'-'};
numbers = numbers.slice(0, 10);
viewValue = '';

for (var i = 0; i < numbers.length; i++) {
viewValue += (char[i]||'') + numbers[i];
}

// set the input to formatted value
el.val(viewValue);

return viewValue;
});

/* called when model is changed outside of the input element */
ngModel.$formatters.push(function(modelValue) {
return modelValue;
});

}
}
});


Here is a demo. enter a number followed by a letter. you will not see the letter. Then type the SAME letter again and you will see the letter. If you hit a different letter you will not see it:
https://plnkr.co/edit/Qp1o8wuNh0doQ4hGZR1E?p=preview

Answer

This is pretty much identical to a question/issue on the angular github. Per here

$parsers is for transforming (a copy of) the $viewValue before "saving" it as $modelValue (and that is what your example is doing). It is not actually changing the $viewValue (although it does update the element's value), which leads to unexpected behaviour.

It shows some examples of what's happening to $viewValue which is not what you might expect in this scenario.

So, you need to do something to update the viewValue. The link suggests a better approach.

See plunker: https://plnkr.co/edit/QhbMtIMZGMxFYW3vVef9?p=preview

// set the input to formatted value
// el.val(viewValue);
ngModel.$viewValue = viewValue; // Update the '$viewValue'
ngModel.$commitViewValue(); //update $$lastCommittedViewValue
ngModel.$render(); // Update the element's displayed value