Angular.js: La fonction ngModelController $ parsers n'est pas appelée de manière cohérente

Créé le 10 août 2015  ·  3Commentaires  ·  Source: angular/angular.js

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.

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 :

  1. L'utilisateur entre '1'.

    • _Avant_ l'analyse :



      • $modelValue : non défini


      • $viewValue : 1


      • element.val() : 1



    • _Après_ l'analyse :



      • $modelValue : 1


      • $viewValue : 1


      • element.val() : 1



  2. L'utilisateur entre « x ».

    • _Avant_ l'analyse :



      • $modelValue : 1


      • $viewValue : 1x


      • element.val() : 1x



    • _Après_ l'analyse :



      • $modelValue : 1


      • $viewValue : 1x (jamais mis à jour depuis l'analyseur fn)


      • element.val() : 1 (mis à jour à partir de la fn de l'analyseur)



  3. L'utilisateur entre « x ».

    • _Avant_ l'analyse :



      • $modelValue : 1


      • $viewValue : 1x (Aucun changement détecté, puisque le précédent $viewValue était également 1x )


      • element.val() : 1x



    • _Pas_ d'analyse (puisqu'aucun changement n'a été détecté dans $viewValue ) !!!



      • $modelValue : 1


      • $viewValue : 1x


      • element.val() : 1x



  4. L'utilisateur entre « x ».

    • _Avant_ l'analyse :



      • $modelValue : 1


      • $viewValue : 1xx


      • element.val() : 1xx



    • _Après_ l'analyse :



      • $modelValue : 1


      • $viewValue : 1xx


      • element.val() : 1



  5. _(...retour à l'étape (2)...)_

Il 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;
});

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.

Tous les 3 commentaires

$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 :

  1. L'utilisateur entre '1'.

    • _Avant_ l'analyse :



      • $modelValue : non défini


      • $viewValue : 1


      • element.val() : 1



    • _Après_ l'analyse :



      • $modelValue : 1


      • $viewValue : 1


      • element.val() : 1



  2. L'utilisateur entre « x ».

    • _Avant_ l'analyse :



      • $modelValue : 1


      • $viewValue : 1x


      • element.val() : 1x



    • _Après_ l'analyse :



      • $modelValue : 1


      • $viewValue : 1x (jamais mis à jour depuis l'analyseur fn)


      • element.val() : 1 (mis à jour à partir de la fn de l'analyseur)



  3. L'utilisateur entre « x ».

    • _Avant_ l'analyse :



      • $modelValue : 1


      • $viewValue : 1x (Aucun changement détecté, puisque le précédent $viewValue était également 1x )


      • element.val() : 1x



    • _Pas_ d'analyse (puisqu'aucun changement n'a été détecté dans $viewValue ) !!!



      • $modelValue : 1


      • $viewValue : 1x


      • element.val() : 1x



  4. L'utilisateur entre « x ».

    • _Avant_ l'analyse :



      • $modelValue : 1


      • $viewValue : 1xx


      • element.val() : 1xx



    • _Après_ l'analyse :



      • $modelValue : 1


      • $viewValue : 1xx


      • element.val() : 1



  5. _(...retour à l'étape (2)...)_

Il 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;
});

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.

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:

Cette page vous a été utile?
0 / 5 - 0 notes