Lors de l'application des valeurs d'entrée pour se conformer à certaines règles à l'aide d'une fonction $parser ngModelController, la fonction $parser n'est pas appelée sur des frappes répétées "invalides", ce qui permet d'ajouter des caractères invalides.
Dans l'exemple ci-dessous, la directive est destinée à supprimer tous les caractères non numériques. cela fonctionne principalement, mais si vous tapez le même caractère non numérique deux fois de suite (par exemple 'xx'), un seul 'x' apparaîtra dans l'entrée. si vous tapez « x » une troisième fois, le « x » sera supprimé.
J'ai également inclus un cas de test en échec.
tout le code s'exécute sur http://codepen.io/visnup/pen/YXgLVq?editors=101
utilisation des directives
<div ng-app="digits">
<input ng-model="number" placeholder="digits only" digits-only />
</div>
directif
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;
});
}
};
});
cas de test en échec
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');
});
});
Cela a déjà été évoqué dans #10700.
$parsers
sert à transformer (une copie de) le $viewValue
avant de le "enregistrer" en tant que $modelValue
(et c'est ce que fait votre exemple). Il ne modifie pas réellement le $viewValue
(bien qu'il mette à jour la valeur de l'élément), ce qui conduit à un comportement inattendu.
Ceci est mieux illustré par un exemple :
$modelValue
: non défini$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1x (jamais mis à jour depuis l'analyseur fn)element.val()
: 1 (mis à jour à partir de la fn de l'analyseur)$modelValue
: 1$viewValue
: 1x (Aucun changement détecté, puisque le précédent $viewValue
était également 1x
)element.val()
: 1x$viewValue
) !!!$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1xxelement.val()
: 1xx$modelValue
: 1$viewValue
: 1xxelement.val()
: 1Il existe plusieurs façons « angulaires » de gérer correctement cela, par exemple :
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;
});
Je ferme ceci, car tout semble fonctionner comme prévu.
@visnup , n'hésitez pas à poser des questions si quelque chose n'est pas clair.
Ce problème se produit avec les frappes répétées à chaque fois que le nouveau $viewValue n'est pas validé. L'utilisation suggérée de $setViewValue dans la collection $parsers est parfois évitée avec une préférence pour définir directement $viewValue dans certains forums.
Les éléments suivants fonctionneraient également :
ngModel.$viewValue = numbers; // Update the '$viewValue'
ngModel.$commitViewValue(); //update $$lastCommittedViewValue
ngModel.$render(); // Update the element's displayed value
Si vous voulez éviter tout piège de récursivité, alors $$lastCommittedViewValue peut être mis à jour directement si cela ne vous dérange pas de violer l'encapsulation. Malheureusement, lorsque la récursivité infinie se produit dans ces fonctions, j'ai vu des cas où elle n'est pas signalée à la console même après un maximum d'occurrences. Cela vaut la peine de prendre quelques minutes pour mettre un point d'arrêt pendant les tests et vérifier les récursions.
Les explications @snaptech combinées forment une solution en or :) :+1:
Commentaire le plus utile
$parsers
sert à transformer (une copie de) le$viewValue
avant de le "enregistrer" en tant que$modelValue
(et c'est ce que fait votre exemple). Il ne modifie pas réellement le$viewValue
(bien qu'il mette à jour la valeur de l'élément), ce qui conduit à un comportement inattendu.Ceci est mieux illustré par un exemple :
$modelValue
: non défini$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1element.val()
: 1$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1x (jamais mis à jour depuis l'analyseur fn)element.val()
: 1 (mis à jour à partir de la fn de l'analyseur)$modelValue
: 1$viewValue
: 1x (Aucun changement détecté, puisque le précédent$viewValue
était également1x
)element.val()
: 1x$viewValue
) !!!$modelValue
: 1$viewValue
: 1xelement.val()
: 1x$modelValue
: 1$viewValue
: 1xxelement.val()
: 1xx$modelValue
: 1$viewValue
: 1xxelement.val()
: 1Il existe plusieurs façons « angulaires » de gérer correctement cela, par exemple :
Stylo mis à jour
Je ferme ceci, car tout semble fonctionner comme prévu.
@visnup , n'hésitez pas à poser des questions si quelque chose n'est pas clair.