Angular.js: La función ngModelController $ parsers no se llama consistentemente

Creado en 10 ago. 2015  ·  3Comentarios  ·  Fuente: angular/angular.js

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.

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:

  1. El usuario ingresa '1'.

    • _Antes_ del análisis:



      • $modelValue : indefinido


      • $viewValue : 1


      • element.val() : 1



    • _Después_ del análisis:



      • $modelValue : 1


      • $viewValue : 1


      • element.val() : 1



  2. El usuario ingresa 'x'.

    • _Antes_ del análisis:



      • $modelValue : 1


      • $viewValue : 1x


      • element.val() : 1x



    • _Después_ del análisis:



      • $modelValue : 1


      • $viewValue : 1x (nunca actualizado desde el analizador fn)


      • element.val() : 1 (actualizado desde el analizador fn)



  3. El usuario ingresa 'x'.

    • _Antes_ del análisis:



      • $modelValue : 1


      • $viewValue : 1x (No se detectaron cambios, ya que el $viewValue también era 1x )


      • element.val() : 1x



    • _No_ análisis (ya que no se detectó ningún cambio en $viewValue ) !!!



      • $modelValue : 1


      • $viewValue : 1x


      • element.val() : 1x



  4. El usuario ingresa 'x'.

    • _Antes_ del análisis:



      • $modelValue : 1


      • $viewValue : 1xx


      • element.val() : 1xx



    • _Después_ del análisis:



      • $modelValue : 1


      • $viewValue : 1xx


      • element.val() : 1



  5. _ (... volver al paso (2) ...) _

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

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.

Todos 3 comentarios

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

  1. El usuario ingresa '1'.

    • _Antes_ del análisis:



      • $modelValue : indefinido


      • $viewValue : 1


      • element.val() : 1



    • _Después_ del análisis:



      • $modelValue : 1


      • $viewValue : 1


      • element.val() : 1



  2. El usuario ingresa 'x'.

    • _Antes_ del análisis:



      • $modelValue : 1


      • $viewValue : 1x


      • element.val() : 1x



    • _Después_ del análisis:



      • $modelValue : 1


      • $viewValue : 1x (nunca actualizado desde el analizador fn)


      • element.val() : 1 (actualizado desde el analizador fn)



  3. El usuario ingresa 'x'.

    • _Antes_ del análisis:



      • $modelValue : 1


      • $viewValue : 1x (No se detectaron cambios, ya que el $viewValue también era 1x )


      • element.val() : 1x



    • _No_ análisis (ya que no se detectó ningún cambio en $viewValue ) !!!



      • $modelValue : 1


      • $viewValue : 1x


      • element.val() : 1x



  4. El usuario ingresa 'x'.

    • _Antes_ del análisis:



      • $modelValue : 1


      • $viewValue : 1xx


      • element.val() : 1xx



    • _Después_ del análisis:



      • $modelValue : 1


      • $viewValue : 1xx


      • element.val() : 1



  5. _ (... volver al paso (2) ...) _

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

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.

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:

¿Fue útil esta página
0 / 5 - 0 calificaciones