Angular.js: ngModelController $parsers 函数未一致调用

创建于 2015-08-10  ·  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. 用户输入“x”。

    • _Before_ 解析:



      • $modelValue : 1


      • $viewValue : 1x


      • element.val() : 1x



    • _After_ 解析:



      • $modelValue : 1


      • $viewValue :1x(从未从解析器 fn 更新)


      • element.val() : 1(从解析器 fn 更新)



  3. 用户输入“x”。

    • _Before_ 解析:



      • $modelValue : 1


      • $viewValue :1x(未检测到变化,因为之前的$viewValue也是1x


      • element.val() :1x



    • _No_ 解析(因为在$viewValue没有检测到任何变化)!!!



      • $modelValue : 1


      • $viewValue :1x


      • element.val() :1x



  4. 用户输入“x”。

    • _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. 用户输入“x”。

    • _Before_ 解析:



      • $modelValue : 1


      • $viewValue : 1x


      • element.val() : 1x



    • _After_ 解析:



      • $modelValue : 1


      • $viewValue :1x(从未从解析器 fn 更新)


      • element.val() : 1(从解析器 fn 更新)



  3. 用户输入“x”。

    • _Before_ 解析:



      • $modelValue : 1


      • $viewValue :1x(未检测到变化,因为之前的$viewValue也是1x


      • element.val() :1x



    • _No_ 解析(因为在$viewValue没有检测到任何变化)!!!



      • $modelValue : 1


      • $viewValue :1x


      • element.val() :1x



  4. 用户输入“x”。

    • _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 时,重复击键都会出现此问题。 有时会避免在 $parsers 集合中建议使用 $setViewValue,而是在某些论坛中倾向于直接设置 $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 等级