【问题标题】:AngularJS custom form validation using $httpAngularJS 自定义表单验证使用 $http
【发布时间】:2013-05-24 12:54:24
【问题描述】:

我有一个如下所示的表单:

<form name="myForm" ng-submit="saveDeployment()">
    <input type="hidden" value="{{item.CloneUrl}}" name="cloneurl" />
    <input type="hidden" value="{{Username}}" name="username" />

    <input type="radio" name="deploymenttype" ng-model="item.deploymentType" value="azure" checked="checked">Azure 
    <br />
    <input type="radio" name="deploymenttype" ng-model="item.deploymentType" value="ftp">FTP

    <div id="azure" ng-show="item.deploymentType=='azure'">
        <label for="azurerepo">Azure Git Repo</label>
        <input type="text" name="azurerepo" ng-model="item.azurerepo" ng-class="{error: myForm.azurerepo.$invalid}" ng-required="item.deploymentType=='azure'" />
    </div>

    <div id="ftp" ng-show="item.deploymentType=='ftp'">
        <label for="ftpserver">FTP Server</label>
        <input type="text" name="ftpserver" ng-model="item.ftpserver" ng-class="{error: myForm.ftpserver.$invalid}" ng-required="item.deploymentType=='ftp'"  />

        <label for="ftppath">FTP Path</label>
        <input type="text" name="ftppath" ng-model="item.ftppath" ng-class="{error: myForm.ftppath.$invalid}" ng-required="item.deploymentType=='ftp'" />

        <label for="ftpusername">FTP Username</label>
        <input type="text" name="ftpusername" ng-model="item.ftpusername" ng-class="{error: myForm.ftpusername.$invalid}" ng-required="item.deploymentType=='ftp'"/>

        <label for="ftppassword">FTP Password</label>
        <input type="password" name="ftppassword" ng-model="item.ftppassword" ng-class="{error: myForm.ftppassword.$invalid}" ng-required="item.deploymentType=='ftp'"/>
    </div>

    <input type="submit" value="Save" ng-disabled="myForm.$invalid"/>

</form>

它的设置使得一旦输入数据,必填字段和保存按钮都可以工作。但是,我的部分验证将是,"Is the user already registered?",我将使用输入的数据使用 $http 通过 POST 访问服务器。

我应该把这个逻辑放在saveDeployment() 函数中还是有更好的地方放它?

*更新:*

我已经实现了以下,它作为元素的属性应用,但它在我不喜欢的每次按键时调用服务器/数据库:

 app.directive('repoAvailable', function ($http, $timeout) { // available
        return {
            require: 'ngModel',
            link: function (scope, elem, attr, ctrl) {
                console.log(ctrl);
                ctrl.$parsers.push(function (viewValue) {
                    // set it to true here, otherwise it will not 
                    // clear out when previous validators fail.
                    ctrl.$setValidity('repoAvailable', true);
                    if (ctrl.$valid) {
                        // set it to false here, because if we need to check 
                        // the validity of the email, it's invalid until the 
                        // AJAX responds.
                        ctrl.$setValidity('checkingRepo', false);

                        // now do your thing, chicken wing.
                        if (viewValue !== "" && typeof viewValue !== "undefined") {
                            $http.post('http://localhost:12008/alreadyregistered',viewValue) //set to 'Test.json' for it to return true.
                                .success(function (data, status, headers, config) {
                                    ctrl.$setValidity('repoAvailable', true);
                                    ctrl.$setValidity('checkingRepo', true);
                                })
                                .error(function (data, status, headers, config) {
                                    ctrl.$setValidity('repoAvailable', false);
                                    ctrl.$setValidity('checkingRepo', true);
                                });
                        } else {
                            ctrl.$setValidity('repoAvailable', false);
                            ctrl.$setValidity('checkingRepo', true);
                        }
                    }
                    return viewValue;
                });

            }
        };
    });

【问题讨论】:

  • 在 saveDeployment 中执行 $http.post 请求,如果失败则向用户显示错误。每次按键都这样做并不是很有说服力,我真的不明白为什么你不能/想要在 saveDeployment 中这样做
  • 我不知道是否有更好的方法。另外,如何从 saveDeployment 返回错误?

标签: javascript angularjs angularjs-directive angularjs-scope


【解决方案1】:

你不需要在指令中发出 $http 请求,更好的地方是控制器。

您可以在控制器中指定方法 - $scope.saveDeployment = function () { // here you make and handle your error on request ... }; 您将错误保存到范围,然后创建一个指令来监视 $scope.yourResponseObject 并根据它设置有效性。

此外,如果您需要输入字段模糊上的请求和错误之类的东西,则需要使用 elem.bind('blur', ...) 创建一个简单的指令,在其中调用 $scope.saveDeployment 并使用回调来处理有效性。

看看例子,可能有类似的东西 - https://github.com/angular/angular.js/wiki/JsFiddle-Examples

【讨论】:

    【解决方案2】:

    使用异步 $http ajax 调用验证表单输入字段是一种常见需求,但我还没有找到任何完整、可重用且易于使用的实现,因此我尽力制作了一个。

    此功能对于检查用户名、电子邮件或其他字段/列是否唯一特别有用,但还有许多其他用例必须使用 ajax 调用验证值(如您的示例中所示)。

    我的解决方案有以下特点:

    • 接受来自$scope 的“检查”函数,该函数进行$http 调用或任何类型的验证(同步或异步)
    • 接受来自$scope 的“门”功能,允许根据值或ngModel 状态绕过检查。
    • 在用户停止输入之前解除“检查”函数的执行
    • 确保只使用最新的$http 调用结果(以防多个被触发并无序返回)。
    • 允许状态绑定,以便 UI 可以适当且方便地响应。
    • 可自定义的去抖动时间、检查/门功能、绑定名称和验证名称。

    我的指令是pmkr-validate-custom (GitHub)。它可用于任何异步验证。我已经在几个版本中对其进行了测试,早在 1.1.5

    这是 Twitter Bootstrap 的示例用法,我在其中检查用户名是否唯一。

    Live Demo

    <form name="the_form" class="form-group has-feedback">
      <div ng-class="{'has-success':userNameUnique.valid, 'has-warning':userNameUnique.invalid}">
        <label for="user_name">Username</label>
        <input 
          name="user_name" 
          ng-model="user.userName" 
          pmkr-validate-custom="{name:'unique', fn:checkUserNameUnique, gate:gateUserNameUnique, wait:500, props:'userNameUnique'}" 
          pmkr-pristine-original="" 
          class="form-control"
        >
        <span ng-show="userNameUnique.valid" class="glyphicon glyphicon-ok form-control-feedback"></span>
        <span ng-show="userNameUnique.invalid" class="glyphicon glyphicon-warning-sign form-control-feedback"></span>
        <i ng-show="userNameUnique.pending" class="glyphicon glyphicon-refresh fa-spin form-control-feedback"></i>
        <p ng-show="userNameUnique.valid" class="alert alert-success">"{{userNameUnique.checkedValue}}" is availiable.</p>
        <p ng-show="userNameUnique.invalid" class="alert alert-warning">"{{userNameUnique.checkedValue}}" is not availiable.</p>
        <button 
          ng-disabled="the_form.$invalid || the_form.user_name.$pristine || userNameUnique.pending" 
          class="btn btn-default"
        >Submit</button>
      </div>
    </form>
    

    样品控制器:

    // Note that this ought to be in a service and referenced to $scope. This is just for demonstration.
    $scope.checkUserNameUnique = function(value) {
      return $http.get(validationUrl+value).then(function(resp) {
        // use resp to determine if value valid
        return isValid; // true or false
      });
    }
    
    // The directive is gated off when this function returns true.
    $scope.gateUserNameUnique = function(value, $ngModel) {
      return !value || $ngModel.$pristine;
    };
    

    如果我做出任何改进,它们将在GitHub 上保持最新,但我还将把此指令及其依赖项的代码放在这里(可能不会更新)。我欢迎通过GitHub issues提出建议或问题!

    angular.module('pmkr.validateCustom', [
      'pmkr.debounce'
    ])
    
    .directive('pmkrValidateCustom', [
      '$q',
      'pmkr.debounce',
      function($q, debounce) {
    
        var directive = {
          restrict: 'A',
          require: 'ngModel',
          // set priority so that other directives can change ngModel state ($pristine, etc) before gate function
          priority: 1,
          link: function($scope, $element, $attrs, $ngModel) {
    
            var opts = $scope.$eval($attrs.pmkrValidateCustom);
    
            // this reference is used as a convenience for $scope[opts.props]
            var props = {
              pending : false,
              validating : false,
              checkedValue : null,
              valid : null,
              invalid : null
            };
            // if opts.props is set, assign props to $scope
            opts.props && ($scope[opts.props] = props);
    
            // debounce validation function
            var debouncedFn = debounce(validate, opts.wait);
            var latestFn = debounce.latest(debouncedFn);
    
            // initially valid
            $ngModel.$setValidity(opts.name, true);
    
            // track gated state
            var gate;
    
            $scope.$watch(function() {
              return $ngModel.$viewValue;
            }, valueChange);
    
            // set model validity and props based on gated state
            function setValidity(isValid) {
              $ngModel.$setValidity(opts.name, isValid);
              if (gate) {
                props.valid = props.invalid = null;
              } else {
                props.valid = !(props.invalid = !isValid);
              }
            }
    
            function validate(val) {
              if (gate) { return; }
              props.validating = true;
              return opts.fn(val);
            }
    
            function valueChange(val) {
    
              if (opts.gate && (gate = opts.gate(val, $ngModel))) {
                props.pending = props.validating = false;
                setValidity(true);
                return;
              }
    
              props.pending = true;
              props.valid = props.invalid = null;
    
              latestFn(val).then(function(isValid) {
                if (gate) { return; }
                props.checkedValue = val;
                setValidity(isValid);
                props.pending = props.validating = false;
              });
    
            }
    
          } // link
    
        }; // directive
    
        return directive;
    
      }
    ])
    
    ;
    
    angular.module('pmkr.debounce', [])
    
    .factory('pmkr.debounce', [
      '$timeout',
      '$q',
      function($timeout, $q) {
    
        var service = function() {
          return debounceFactory.apply(this, arguments);
        };
        service.immediate = function() {
          return debounceImmediateFactory.apply(this, arguments);
        };
        service.latest = function() {
          return debounceLatestFactory.apply(this, arguments);
        };
    
        function debounceFactory(fn, wait) {
    
          var timeoutPromise;
    
          function debounced() {
    
            var deferred = $q.defer();
    
            var context = this;
            var args = arguments;
    
            $timeout.cancel(timeoutPromise);
    
            timeoutPromise = $timeout(function() {
              deferred.resolve(fn.apply(context, args));
            }, wait);
    
            return deferred.promise;
    
          }
    
          return debounced;
    
        }
    
        function debounceImmediateFactory(fn, wait) {
    
          var timeoutPromise;
    
          function debounced() {
    
            var deferred = $q.defer();
    
            var context = this;
            var args = arguments;
    
            if (!timeoutPromise) {
              deferred.resolve(fn.apply(context, args));
              // return here?
            }
    
            $timeout.cancel(timeoutPromise);
            timeoutPromise = $timeout(function() {
              timeoutPromise = null;
            }, wait);
    
            return deferred.promise;
    
          }
    
          return debounced;
    
        }
    
        function debounceLatestFactory(fn) {
    
          var latestArgs;
    
          function debounced() {
    
            var args = latestArgs = JSON.stringify(arguments);
    
            var deferred = $q.defer();
    
            fn.apply(this, arguments).then(function(res) {
              if (latestArgs === args) {
                deferred.resolve(res);
              }
            }, function(res) {
              if (latestArgs === args) {
                deferred.reject(res);
              }
            });
    
            return deferred.promise;
    
          }
    
          return debounced;
    
        }
    
        return service;
    
      }
    ])
    
    ;
    
    angular.module('pmkr.pristineOriginal', [])
    
    .directive('pmkrPristineOriginal', [
      function() {
    
        var directive = {
          restrict : 'A',
          require : 'ngModel',
          link: function($scope, $element, $atts, $ngModel) {
    
            var pristineVal = null;
    
            $scope.$watch(function() {
              return $ngModel.$viewValue;
            }, function(val) {
              // set pristineVal to newVal the first time this function runs
              if (pristineVal === null) {
                pristineVal = $ngModel.$isEmpty(val) ? '' : val.toString();
              }
    
              // newVal is the original value - set input to pristine state
              if (pristineVal === val) {
                $ngModel.$setPristine();
              }
    
            });
    
          }
        };
    
        return directive;
    
      }
    ])
    
    ;
    

    【讨论】:

      【解决方案3】:

      我的解决方案来自 Kosmetika 的想法。

      我使用了angular-ui 项目并在通过$http 调用Web 服务的控制器上设置了一个onBlur 回调。

      这会将控制器/模型属性设置为 true 或 false。

      然后我有一个 &lt;span&gt; 使用 ng-show 来观察控制器/模型属性,因此当 Web 服务返回时它会显示用户信息

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-16
        • 1970-01-01
        相关资源
        最近更新 更多