Cuando se imponen los valores de entrada para que se ajusten a ciertas reglas usando una función ngModelController $ parser, la función $ parser no se llama en las pulsaciones repetidas de teclas "no válidas", lo que permite que se agreguen caracteres no válidos.
En el siguiente ejemplo, la directiva está destinada a eliminar todos los caracteres que no sean dígitos. en su mayoría funciona, pero si escribe el mismo carácter que no es un dígito dos veces seguidas (por ejemplo, 'xx'), aparecerá una sola 'x' en la entrada. si escribe 'x' por tercera vez, eliminará la 'x'.
También he incluido un caso de prueba fallido.
todo el código se ejecuta en http://codepen.io/visnup/pen/YXgLVq?editors=101
uso de directiva
<div ng-app="digits">
<input ng-model="number" placeholder="digits only" digits-only />
</div>
directiva
angular
.module('digits', [])
.directive('digitsOnly', function() {
return {
require: 'ngModel',
link: function link(scope, element, attrs, ngModel) {
ngModel.$parsers.push(function(value) {
var numbers = value.replace(/\D/g, '');
element.val(numbers);
return numbers;
});
}
};
});
caso de prueba fallido
describe('digits', function() {
beforeEach(angular.mock.module('digits'));
let scope, input;
beforeEach(inject(function($compile, $rootScope) {
scope = $rootScope;
input = $compile('<input ng-model="number" digits-only />')(scope);
}));
// works
it('should block non-digits', function() {
input.val('111x');
input.triggerHandler('input');
expect(input.val()).to.equal('111');
});
// doesn't work
it('should not flash incorrect input', function() {
input.val('111x');
input.triggerHandler('input');
input.val('111x');
input.triggerHandler('input');
expect(input.val()).to.equal('111');
});
});
Esto se ha mencionado antes en el # 10700.
$parsers
es para transformar (una copia de) el $viewValue
antes de "guardarlo" como $modelValue
(y eso es lo que está haciendo su ejemplo). En realidad, no está cambiando el $viewValue
(aunque actualiza el valor del elemento), lo que conduce a un comportamiento inesperado.
Esto se ilustra mejor con un ejemplo:
$modelValue
: indefinido$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1x (nunca actualizado desde el analizador fn)element.val()
: 1 (actualizado desde el analizador fn)$modelValue
: 1$viewValue
: 1x (No se detectaron cambios, ya que el $viewValue
también era 1x
)element.val()
: 1x$viewValue
) !!!$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1xxelement.val()
: 1xx$modelValue
: 1$viewValue
: 1xxelement.val()
: 1Hay varias formas "angulares" de manejar esto correctamente, por ejemplo:
ngModel.$parsers.push(function (value) {
var numbers = value.replace(/\D/g, '');
if (numbers !== value) {
ngModel.$setViewValue(numbers); // Update the `$viewValue`
ngModel.$render(); // Update the element's displayed value
}
return numbers;
});
Estoy cerrando esto, ya que todo parece funcionar como se esperaba.
@visnup , no dude en hacer un seguimiento con preguntas si algo no está claro.
Este problema se produce con las pulsaciones de teclas repetidas cada vez que no se confirma el valor de $ viewValue nuevo. El uso sugerido de $ setViewValue en la colección de $ parsers a veces se evita con una preferencia por establecer directamente $ viewValue en algunos foros.
Lo siguiente también funcionaría:
ngModel.$viewValue = numbers; // Update the '$viewValue'
ngModel.$commitViewValue(); //update $$lastCommittedViewValue
ngModel.$render(); // Update the element's displayed value
Si desea evitar cualquier problema de recursividad, entonces $$ lastCommittedViewValue podría actualizarse directamente si no le importa violar la encapsulación. Desafortunadamente, cuando ocurre una recursividad infinita en estas funciones, he visto casos en los que no se informa a la consola incluso después de un número máximo de ocurrencias. Vale la pena tomarse unos minutos para establecer un punto de interrupción durante las pruebas y verificar las recursiones.
Las explicaciones de @snaptech combinadas forman una solución de oro :): +1:
Comentario más útil
$parsers
es para transformar (una copia de) el$viewValue
antes de "guardarlo" como$modelValue
(y eso es lo que está haciendo su ejemplo). En realidad, no está cambiando el$viewValue
(aunque actualiza el valor del elemento), lo que conduce a un comportamiento inesperado.Esto se ilustra mejor con un ejemplo:
$modelValue
: indefinido$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1x (nunca actualizado desde el analizador fn)element.val()
: 1 (actualizado desde el analizador fn)$modelValue
: 1$viewValue
: 1x (No se detectaron cambios, ya que el$viewValue
también era1x
)element.val()
: 1x$viewValue
) !!!$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1xxelement.val()
: 1xx$modelValue
: 1$viewValue
: 1xxelement.val()
: 1Hay varias formas "angulares" de manejar esto correctamente, por ejemplo:
Bolígrafo actualizado
Estoy cerrando esto, ya que todo parece funcionar como se esperaba.
@visnup , no dude en hacer un seguimiento con preguntas si algo no está claro.