Angular.js: Функция ngModelController $ parsers не вызывается последовательно

Созданный на 10 авг. 2015  ·  3Комментарии  ·  Источник: angular/angular.js

При приведении входных значений в соответствие с определенными правилами с помощью функции 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 (хотя и обновляет значение элемента), что приводит к неожиданному поведению.

Это лучше проиллюстрировать на примере:

  1. Пользователь вводит «1».

    • _Before_ парсинг:



      • $modelValue : не определено


      • $viewValue : 1


      • element.val() : 1



    • _After_ парсинг:



      • $modelValue : 1


      • $viewValue : 1


      • element.val() : 1



  2. Пользователь вводит «х».

    • _Before_ парсинг:



      • $modelValue : 1


      • $viewValue : 1x


      • element.val() : 1x



    • _After_ парсинг:



      • $modelValue : 1


      • $viewValue : 1x (никогда не обновляется из парсера fn)


      • element.val() : 1 (обновлено из парсера fn)



  3. Пользователь вводит «х».

    • _Before_ парсинг:



      • $modelValue : 1


      • $viewValue : 1x (изменений не обнаружено, поскольку предыдущий $viewValue также был 1x )


      • element.val() : 1x



    • _Нет_ синтаксического анализа (поскольку в $viewValue изменений не обнаружено) !!!



      • $modelValue : 1


      • $viewValue : 1x


      • element.val() : 1x



  4. Пользователь вводит «х».

    • _Before_ синтаксический анализ:



      • $modelValue : 1


      • $viewValue : 1xx


      • element.val() : 1xx



    • _After_ парсинг:



      • $modelValue : 1


      • $viewValue : 1xx


      • element.val() : 1



  5. _ (... вернуться к шагу (2) ...) _

Есть несколько "угловых" способов справиться с этим, например:

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 , не стесняйтесь

Все 3 Комментарий

$parsers предназначен для преобразования (копии) $viewValue перед "сохранением" его как $modelValue (и это то, что делает ваш пример). Фактически он не меняет $viewValue (хотя и обновляет значение элемента), что приводит к неожиданному поведению.

Это лучше проиллюстрировать на примере:

  1. Пользователь вводит «1».

    • _Before_ парсинг:



      • $modelValue : не определено


      • $viewValue : 1


      • element.val() : 1



    • _After_ парсинг:



      • $modelValue : 1


      • $viewValue : 1


      • element.val() : 1



  2. Пользователь вводит «х».

    • _Before_ парсинг:



      • $modelValue : 1


      • $viewValue : 1x


      • element.val() : 1x



    • _After_ парсинг:



      • $modelValue : 1


      • $viewValue : 1x (никогда не обновляется из парсера fn)


      • element.val() : 1 (обновлено из парсера fn)



  3. Пользователь вводит «х».

    • _Before_ парсинг:



      • $modelValue : 1


      • $viewValue : 1x (изменений не обнаружено, поскольку предыдущий $viewValue также был 1x )


      • element.val() : 1x



    • _Нет_ синтаксического анализа (поскольку в $viewValue изменений не обнаружено) !!!



      • $modelValue : 1


      • $viewValue : 1x


      • element.val() : 1x



  4. Пользователь вводит «х».

    • _Before_ синтаксический анализ:



      • $modelValue : 1


      • $viewValue : 1xx


      • element.val() : 1xx



    • _After_ парсинг:



      • $modelValue : 1


      • $viewValue : 1xx


      • element.val() : 1



  5. _ (... вернуться к шагу (2) ...) _

Есть несколько "угловых" способов справиться с этим, например:

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:

Была ли эта страница полезной?
0 / 5 - 0 рейтинги