角度验证:回滚ngModel。$ modelValue更新?

时间:2014-07-24 17:15:36

标签: angularjs angularjs-directive

我写了一个异步验证器。它调用远程端点并检查我的字段是否唯一。如果远程端点是唯一的,则应返回 true ,否则返回 false 。我的验证员:

    module.directive('shouldBeUnique', ['$parse', '$timeout', '$http', '$q', function ($parse, $timeout, $http, $q) {
    return {
        // restrict to an attribute type.
        restrict: 'A',
        require: 'ngModel',
        scope: {
            getEndPoint: '&'
        },
        priority: 1,
        link: function (scope, element, attrs, ngModel) {
            function isUnique(value) {
                $timeout(function() {
                    ngModel.$setValidity('shouldBeUnique', false);
                });

                //Cancels any ongoing http request
                if(scope.canceler) {
                    scope.canceler.resolve();
                }

                if(!value) {
                    return;
                }

                ngModel.validationInProgress = true;
                var canceler = scope.canceler = $q.defer();
                var endpointDetails = scope.getEndPoint();
                var params = {};
                params[endpointDetails.paramName] = value;
                var request = $http({
                    url: endpointDetails.url,
                    method: 'GET',
                    params: params,
                    timeout: canceler.promise
                });

                request.success(function(data) {
                    var valid = false;
                    if(data === 'true') {
                        valid = true;
                    }

                    ngModel.$setValidity('shouldBeUnique', valid);
                    ngModel.validationInProgress = false;
                });

                request.error(function() {
                    ngModel.validationInProgress = false;
                });
            }

            ngModel.$formatters.push(function(value) {
                isUnique(value);
                return value;
            });

            ngModel.$parsers.push(function (value) {
                ngModel.$setValidity('shouldBeUnique', true);
                ngModel.$dirty = true;
                if(ngModel.$valid) {
                    isUnique(value);
                }

                return value;
            });
        }
    };
}]);`

我的验证工作正常。它的工作方式是将setValidity初始化为true,以便更新modelValue。然后异步检查实际有效性,并相应地调用ngModel.setValidity

我遇到的唯一问题是ngModel.$modelValue在检查有效性之前会更新,因此它可能包含无效数据。有没有办法将提交回滚到modelValue

我可以找到$rollbackViewValue,但没有找到模型。或者有什么方法可以推迟ngModel.$modelValue更新到异步调用完成的时间。

2 个答案:

答案 0 :(得分:1)

能够解决问题。当异步调用正在进行时,提交一个临时值,例如“VALIDATION_PENDING”,当验证完成时,执行ngModel。$ commitViewValue(true)。我正在传递真实再次触发解析器。你的解析器应该处理ngModel。$ modelValue ==='VALIDATION_PENDING'时的情况。在这种情况下,不要进行异步调用,只需从Parser返回$ viewValue。

代码:

module.directive('shouldBeUnique', ['$parse', '$timeout', '$http', '$q', 'VALIDATION_PENDING', function ($parse, $timeout, $http, $q, VALIDATION_PENDING) {
    return {
        // restrict to an attribute type.
        restrict: 'A',
        require: 'ngModel',
        scope: {
            getEndPoint: '&'
        },
        priority: 1,
        link: function (scope, element, attrs, ngModel) {
            var lastValidationStatus;

            function isUnique(value) {
                //Cancels any ongoing http request
                if(scope.canceler) {
                    scope.canceler.resolve();
                }

                var canceler = scope.canceler = $q.defer();
                var endpointDetails = scope.getEndPoint();
                var params = {};
                params[endpointDetails.paramName] = value;
                var httpDeferred = $http({
                    url: endpointDetails.url,
                    method: 'GET',
                    params: params,
                    timeout: canceler.promise
                });

                ngModel.validationInProgress = true;
                return httpDeferred;
            }

            function setValidity(httpDeferred) {
                httpDeferred.success(function(data) {
                    lastValidationStatus = false;
                    if(data === 'true') {
                        lastValidationStatus = true;
                    }

                    ngModel.validationInProgress = false;
                    ngModel.$commitViewValue(true);
                });

                httpDeferred.error(function() {
                    ngModel.validationInProgress = false;
                });
            }

            ngModel.$formatters.push(function(value) {
                if(value) {
                    var httpDeferred = isUnique(value);
                    setValidity(httpDeferred);
                }
                return value;
            });

            ngModel.$parsers.push(function (value) {
                ngModel.$setValidity('shouldBeUnique', true);

                if(ngModel.$valid) {
                    var httpDeferred;

                    if (ngModel.$modelValue === VALIDATION_PENDING && !ngModel.validationInProgress) {
                        $timeout(function () {
                            ngModel.$setValidity('shouldBeUnique', lastValidationStatus);
                        });
                        if (lastValidationStatus) {
                            return value;
                        }
                        return undefined;
                    }
                    else {
                        httpDeferred = isUnique(value);
                        setValidity(httpDeferred);
                        return VALIDATION_PENDING;
                    }
                }

                return undefined;
            });
        }
    };
}]);

module.constant('VALIDATION_PENDING', '###REMOTE_VALIDATION_IN_PROGRESS###');

答案 1 :(得分:0)

我认为你处理验证的方式太复杂了。您可以使用ngModel的$viewChangeListeners回调数组来添加验证。只要输入发生更改,就会触发此回调数组,这是注册ngChange指令回调的位置。

解决方案可以是这样的(您可以在 PLUNKER 中查看我的示例):

  directive('uniqueData', function($http, $q, $timeout) {
    return {
      require: 'ngModel',
      link: function(scope, elem, attr, ngModel) {

        ngModel.$setValidity('uniqueData', true); // default

        var canceller,
          request = function() {

            // set validity to true whenever changes are made
            ngModel.$setValidity('uniqueData', true);

            if(canceller)
              canceller.resolve();

            canceller = $q.defer();

            ngModel.isInProgress = true;

            // you can remove the timeout
            // it only simulates a timed request
            $timeout(function() {

              $http({
                method: 'GET',
                url: 'users.json',
                timeout: canceller.promise
              }).success(function(data) {
                for(var index in data) {
                  var user = data[index];
                  if(user.username == ngModel.$modelValue) {
                    ngModel.$setValidity('uniqueData', false);
                    break;
                  }
                }
              })['finally'](function() {
                ngModel.isInProgress = false;
              });

            }, 2000);
          };

        ngModel.$viewChangeListeners.push(request);

      }
    }
  });