Angularjs 如何正确应用范围,以便顺序无关紧要

Angularjs 如何正确应用范围,以便顺序无关紧要,angularjs,unit-testing,jasmine,Angularjs,Unit Testing,Jasmine,我正在尝试为一个指令编写一个单元测试,该指令将一个值与其他输入字段相匹配。问题是,如果我在应用指令的元素之前定义要匹配的元素,它就可以正常工作,否则就会失败 当模板为 tpl = '<input name="verifyNewPassword" ng-model="verifyNewPassword" type="password"/>'; tpl += '<input name="newPassword" ng-model="newPassword" type="passwo

我正在尝试为一个指令编写一个单元测试,该指令将一个值与其他输入字段相匹配。问题是,如果我在应用指令的元素之前定义要匹配的元素,它就可以正常工作,否则就会失败

当模板为

tpl = '<input name="verifyNewPassword" ng-model="verifyNewPassword" type="password"/>';
tpl += '<input name="newPassword" ng-model="newPassword" type="password" equals-to="userForm.verifyNewPassword"/>';
tpl = '<input name="newPassword" ng-model="newPassword" type="password" equals-to="userForm.verifyNewPassword"/>';
tpl+='<input name="verifyNewPassword" ng-model="verifyNewPassword" type="password"/>';
以下是我的测试代码:

describe('Unit: Testing Directives', function() {
    var elm, scope;

    beforeEach(function() {
        module('mctApp');

        inject(function($rootScope, $compile) {
            scope = $rootScope.$new();
        });
    });

    function compileDirective(tpl) {
        if(!tpl) {
            tpl = '<input name="newPassword" ng-model="newPassword" type="password" equals-to="userForm.verifyNewPassword"/>';
            tpl += '<input name="verifyNewPassword" ng-model="verifyNewPassword" type="password"/>';            
        }
        tpl = '<form name="userForm">' + tpl + '</form>';

        inject(function($compile) {
            var form = $compile(tpl)(scope);
        });

        scope.$digest();

    }

    it('must be valid form as both values are equal', function() {
        scope.newPassword = 'abcdef';
        scope.verifyNewPassword = 'abcdef';
        compileDirective();                 
        expect(scope.userForm.$valid).toBeTruthy();
    });
});
description('单元:测试指令',函数()){
var-elm,范围;
beforeach(函数(){
模块(“mctApp”);
注入(函数($rootScope,$compile){
scope=$rootScope.$new();
});
});
函数compileDirective(tpl){
如果(!tpl){
第三方物流='';
第三方物流+=”;
}
tpl=''+tpl+'';
注入(函数($compile){
变量形式=$compile(tpl)(范围);
});
范围。$digest();
}
它('必须是有效形式,因为两个值相等',函数(){
scope.newPassword='abcdef';
scope.verifyNewPassword='abcdef';
compileDirective();
expect(scope.userForm.$valid).toBeTruthy();
});
});

测试失败,因为当
监视开始启动时,
$viewValue
等于
ngModelController,因此字段有效性设置为false,表单无效

当您在输入
ngModel
分配给的范围内观察对象时(该范围已设置为“abcdef”),手表只被调用一次。相反,如果您查看要比较的输入的
ngModel.$viewValue
,它保证初始状态始终正确,而不管DOM中输入的顺序如何

我还认为,观察这个值更有意义,因为它是你正在比较的值

.directive('equalsTo', function() {
    return {
        require: 'ngModel',
        link: function(scope, elm, attrs, ctrl) {
            var sc = scope;
            scope.$watch(attrs.equalsTo + '.$viewValue', function() {
                var eqCtrl = scope.$eval(attrs.equalsTo);
                console.log('Value1: ' + ctrl.$viewValue + ', Value2: ' + eqCtrl.$viewValue);
                if (ctrl.$viewValue===eqCtrl.$viewValue || (!!!ctrl.$viewValue && !!!eqCtrl.$viewValue)) {
                    ctrl.$setValidity('equalsTo', true);
                    eqCtrl.$setValidity('equalsTo', true);
                } else {
                    ctrl.$setValidity('equalsTo', false);
                    eqCtrl.$setValidity('equalsTo', false);
                }
            });
        }
    };
})

注意


如果您使用的是angular 1.3+,那么值得一看新的验证器管道,因为它是解决相同问题的更优雅的方法。

首先,对于您的模型场景,有一个比同步验证两个输入更好的UX解决方案。第一个字段(密码)应仅根据您的密码格式限制进行验证,并且仅应检查第二个字段(密码确认)是否与密码相等。这有助于用户在选择有效和可重新键入的密码时保持理智。(换句话说,密码确认是第二次输入的唯一领域。如果你把确认搞砸了,它不会突然使之前有效的输入失效。)

(如果使用AngularJS版本
<1.3
,则使用
$parsers
$setValidity
而不是
$validator

测试指令将是轻而易举的事,因为您只需要调整模型


作为旁注,即使您决定要同时验证两个输入,也最好根据相应的模型值验证每个输入(对两个输入使用my
equalTo
指令),而不是强制兄弟控制器之间进行直接通信。

我认为在应用范围时需要使用$超时处理$digest进程,因为一旦完成$digest,它就会应用作用域

这就是我过去应用范围的方式

$timeout(function(){
    $scope.$apply()
}
在测试中,可以使用$timeout.flush()同步刷新延迟函数队列

<input type="password" ng-model="password" required ng-pattern="/^(?=.*\w)(?=.*\W)/">
<input type="password" ng-model="passwordConfirmation" equal-to="password">
.directive('equalTo', ['$parse', function ($parse) {
    return {
        require: 'ngModel',
        compile: function (element, attrs) {
            var getOtherValue = $parse(attrs.equalTo);

            return function link ($scope, $element, $attrs, ngModelCtrl) {
                ngModelCtrl.$validators.equalTo = function (value) {
                    return (value === getOtherValue($scope));
                };
            };
        }
    };
}])
$timeout(function(){
    $scope.$apply()
}