AngularJS指令基于指令名的第一个字符而中断

时间:2014-08-18 13:15:16

标签: angularjs angularjs-directive

我写了一个表现出一些奇怪行为的Angular指令。该指令向$ parsers添加一个函数,以限制用户根据正则表达式模式键入的内容。如果当前文本与模式不匹配,则解析器会将文本还原为该字段的上一个值。

因此,当文本被恢复时,Angular会将其检测为字段值的更改并再次进入解析器。这通常很好,因为传递给解析器的值现在有效,但我遇到了一个非常奇怪的怪癖。

我得到这个指令后,我决定更改它的名字。我这样做了,突然验证失败了。我的错误处理程序报告了过多的递归。当我调试代码时,我发现输入无效字符后第二次调用解析器会将字段值参数显示为'undefined'。结果,我的代码将值视为无效并尝试再次返回,这导致另一个调用带有'undefined'值等的解析器,直到发生堆栈溢出。

我更改了指令名称,再次调试,一切都突然开始正常工作!对解析器的第二次调用具有正确的值而不是'undefined'。

我做了一些游戏并发现我可以通过更改指令名称的第一个字符来重新创建此错误。以'a'到'm'字符开头的指令名称运行良好,但以'n'到'z'开头的名称破了(好吧,我承认,我没有尝试所有26个字符,但是一个字符的样本显示样本中指令名称的第一个字母位于字母表前半部分的所有名称以及第一个字母位于字母表后半部分的所有运行都失败了。

我把一个带有我的代码的plunker放在一起来演示它:

http://plnkr.co/edit/k8Hpk2jsMCS6xjOKiES5

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
    $scope.someNumber;
});

app.directive('formattedWithPattern', function () {
    // Formats an input field as a positive integer.
    // Usage:
    //  <input type="text" data-integer-format>
    // Creates:
    //  <input type="text" data-integer-format>
    return {
        require: 'ngModel',
        link: function (scope, element, attr, ctrl) {
            if (!ctrl) return;

            var pattern = attr.ngPattern;
            // get the pattern between the slashes and any modifiers
            var r = new RegExp("^/(.*)/(.*)$");
            var matches = r.exec(pattern);
            var regex;
            if (matches) {
                regex = new RegExp('^' + matches[1] + '$', matches[2]);
            }
            var lastText = '';
            var reverted = false;

            function fromUser(text) {
                var m = regex.exec(text);
                if (m) {
                    // join matches together into a single string
                    lastText = m[0];
                    if (lastText != text) {
                        // the original text contained some invalid characters
                        ctrl.$setViewValue(lastText);
                        ctrl.$render();
                    }
                }
                else {
                    // nothing in the text matched the regular expression...
                    // revert to the last good value
                    if (text != lastText) {
                        ctrl.$setViewValue(lastText);
                        ctrl.$render();
                    }
                }
                return lastText;
            }

            ctrl.$parsers.unshift(fromUser);
        }
    };
});

这是一个使用它的例子(也来自plunker):

<body ng-controller="MainCtrl">
    <input type="text" name="testNumber" id="testNumber" 
       data-ng-model="someNumber" data-ng-required="true"
       ng-pattern="/[\+\-]?[0-9]*(\.[0-9]*)?/" 
       formatted-with-pattern />
    {{zip}}
</body>

出于某种原因,plunker的行为与我在计算机上测试时看到的行为略有不同。所有失败仍然在字母表的上限范围内,但失败从'o'开始而不是'n'。

如果您将app.js和index.html中指令的名称更改为以任何字符“o”到“z”开头并重新运行plunker,您可以轻松查看该行为。上面的指令使用数字模式,因此当指令名称为“valid”时,该指令不允许除0-9,。,+和 - 之外的任何字符。当名称为“无效”时,该指令还允许使用字符,因为在不实际更改输入字段值的情况下,对解析器的递归调用正在中断。

这让我觉得非常奇怪。我没有在网上找到任何其他的提及,所以我想我会把它扔出去。有没有人遇到过这样的事情?这是AngularJS中的错误吗?有没有人知道一个解决方法,除了确保我的指令名称以字符a到m开头?

1 个答案:

答案 0 :(得分:2)

您可以引发指令priority:以确保执行顺序如下:

return {
  require: 'ngModel',
  priority: 1, // default is 0
  link: function (scope, element, attr, ctrl) {
    ...
  }
};

它将确保您的指令postLink函数将在ng-requiredng-pattern之后运行。

示例Plunker: http://plnkr.co/edit/dy1zCq9F8EejYWo1m4Oe?p=preview

您也可以直接使用ctrl.$viewValue来避免指令执行顺序问题。

实际上,恕我直言,使用ctrl.$viewValue更有意义,因为如果它无效,你想重新渲染视图,所以你需要一个真正的视图值,而不是已经通过其他解析器的值

为此,您可以从以下位置更改解析器:

function fromUser(text) {
  ...
}

改为:

function fromUser() {
  var text = ctrl.$viewValue;
  ...
}

示例Plunker: http://plnkr.co/edit/FA2Fq4aNlUrcdUMoopuX?p=preview

希望这有帮助。