Ao impor valores de entrada para estar em conformidade com certas regras usando uma função ngModelController $ parser, a função $ parser não é chamada em pressionamentos de tecla "inválidos" repetidos, o que permite que caracteres inválidos sejam adicionados.
No exemplo abaixo, a diretiva destina-se a remover todos os caracteres não-dígitos. geralmente funciona, mas se você digitar o mesmo caractere não-dígito duas vezes seguidas (por exemplo, 'xx'), um único 'x' aparecerá na entrada. se você digitar 'x' uma terceira vez, o 'x' será removido.
Também incluí um caso de teste com falha.
todo o código está sendo executado em http://codepen.io/visnup/pen/YXgLVq?editors=101
uso diretivo
<div ng-app="digits">
<input ng-model="number" placeholder="digits only" digits-only />
</div>
diretriz
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 teste reprovado
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');
});
});
Isso já foi mencionado em # 10700.
$parsers
é para transformar (uma cópia de) $viewValue
antes de "salvá-lo" como $modelValue
(e é isso que seu exemplo está fazendo). Na verdade, ele não está mudando $viewValue
(embora atualize o valor do elemento), o que leva a um comportamento inesperado.
Isso é melhor ilustrado com um exemplo:
$modelValue
: indefinido$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1x (nunca atualizado do analisador fn)element.val()
: 1 (atualizado do analisador fn)$modelValue
: 1$viewValue
: 1x (Nenhuma alteração detectada, já que $viewValue
também era 1x
)element.val()
: 1x$viewValue
) !!!$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1xxelement.val()
: 1xx$modelValue
: 1$viewValue
: 1xxelement.val()
: 1Existem várias maneiras "angulares" de lidar adequadamente com isso, por exemplo:
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;
});
Estou encerrando, pois tudo parece funcionar conforme o esperado.
@visnup ,
Esse problema ocorre com pressionamentos de tecla repetidos sempre que o novo $ viewValue não for confirmado. O uso sugerido de $ setViewValue na coleção $ parsers às vezes é evitado com a preferência de definir diretamente $ viewValue em alguns fóruns.
O seguinte também funcionaria:
ngModel.$viewValue = numbers; // Update the '$viewValue'
ngModel.$commitViewValue(); //update $$lastCommittedViewValue
ngModel.$render(); // Update the element's displayed value
Se você quiser evitar quaisquer armadilhas de recursão, $$ lastCommittedViewValue pode ser atualizado diretamente se você não se importar em violar o encapsulamento. Infelizmente, quando a recursão infinita ocorre nessas funções, tenho visto casos em que ela não é relatada ao console, mesmo após o máximo de ocorrências. Vale a pena dedicar alguns minutos para colocar um ponto de interrupção durante o teste e verificar quaisquer recursões.
As explicações de @snaptech combinadas formam uma solução de ouro :): +1:
Comentários muito úteis
$parsers
é para transformar (uma cópia de)$viewValue
antes de "salvá-lo" como$modelValue
(e é isso que seu exemplo está fazendo). Na verdade, ele não está mudando$viewValue
(embora atualize o valor do elemento), o que leva a um comportamento inesperado.Isso é melhor ilustrado com um exemplo:
$modelValue
: indefinido$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1x (nunca atualizado do analisador fn)element.val()
: 1 (atualizado do analisador fn)$modelValue
: 1$viewValue
: 1x (Nenhuma alteração detectada, já que$viewValue
também era1x
)element.val()
: 1x$viewValue
) !!!$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1xxelement.val()
: 1xx$modelValue
: 1$viewValue
: 1xxelement.val()
: 1Existem várias maneiras "angulares" de lidar adequadamente com isso, por exemplo:
Caneta atualizada
Estou encerrando, pois tudo parece funcionar conforme o esperado.
@visnup ,