При приведении входных значений в соответствие с определенными правилами с помощью функции ngModelController $ parser функция $ parser не вызывается при повторяющихся «недопустимых» нажатиях клавиш, что позволяет добавлять недопустимые символы.
В приведенном ниже примере директива предназначена для удаления всех нецифровых символов. в основном это работает, но если вы наберете один и тот же нецифровой символ дважды подряд (например, «xx»), во входных данных появится один «x». если вы наберете «x» в третий раз, он удалит «x».
Я также включил неудачный тестовый пример.
весь код работает на http://codepen.io/visnup/pen/YXgLVq?editors=101
использование директивы
<div ng-app="digits">
<input ng-model="number" placeholder="digits only" digits-only />
</div>
директива
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;
});
}
};
});
неудачный тестовый пример
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');
});
});
Об этом уже говорилось в # 10700.
$parsers
предназначен для преобразования (копии) $viewValue
перед "сохранением" его как $modelValue
(и это то, что делает ваш пример). Фактически он не меняет $viewValue
(хотя и обновляет значение элемента), что приводит к неожиданному поведению.
Это лучше проиллюстрировать на примере:
$modelValue
: не определено$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1x (никогда не обновляется из парсера fn)element.val()
: 1 (обновлено из парсера fn)$modelValue
: 1$viewValue
: 1x (изменений не обнаружено, поскольку предыдущий $viewValue
также был 1x
)element.val()
: 1x$viewValue
изменений не обнаружено) !!!$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1xxelement.val()
: 1xx$modelValue
: 1$viewValue
: 1xxelement.val()
: 1Есть несколько "угловых" способов справиться с этим, например:
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;
});
Я закрываю это, так как вроде все работает, как ожидалось.
@visnup , не стесняйтесь
Эта проблема возникает при повторном нажатии клавиш каждый раз, когда новое значение $ viewValue не фиксируется. Предлагаемого использования $ setViewValue в коллекции $ parsers иногда избегают, предпочитая прямо устанавливать $ viewValue на некоторых форумах.
Следующее также будет работать:
ngModel.$viewValue = numbers; // Update the '$viewValue'
ngModel.$commitViewValue(); //update $$lastCommittedViewValue
ngModel.$render(); // Update the element's displayed value
Если вы хотите вообще избежать каких-либо ошибок рекурсии, то $$ lastCommittedViewValue можно обновить напрямую, если вы не возражаете против нарушения инкапсуляции. К сожалению, когда в этих функциях действительно происходит бесконечная рекурсия, я видел случаи, когда об этом не сообщалось на консоль даже после максимального количества вхождений. Стоит потратить несколько минут, чтобы поставить точку останова во время тестирования и проверить наличие рекурсий.
Объяснения @gkalpak и @snaptech вместе составляют прекрасное решение :): +1:
Самый полезный комментарий
$parsers
предназначен для преобразования (копии)$viewValue
перед "сохранением" его как$modelValue
(и это то, что делает ваш пример). Фактически он не меняет$viewValue
(хотя и обновляет значение элемента), что приводит к неожиданному поведению.Это лучше проиллюстрировать на примере:
$modelValue
: не определено$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1x (никогда не обновляется из парсера fn)element.val()
: 1 (обновлено из парсера fn)$modelValue
: 1$viewValue
: 1x (изменений не обнаружено, поскольку предыдущий$viewValue
также был1x
)element.val()
: 1x$viewValue
изменений не обнаружено) !!!$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1xxelement.val()
: 1xx$modelValue
: 1$viewValue
: 1xxelement.val()
: 1Есть несколько "угловых" способов справиться с этим, например:
Обновленное перо
Я закрываю это, так как вроде все работает, как ожидалось.
@visnup , не стесняйтесь