Javascript 使用$http进行AngularJS自定义表单验证

Javascript 使用$http进行AngularJS自定义表单验证,javascript,angularjs,angularjs-directive,angularjs-scope,Javascript,Angularjs,Angularjs Directive,Angularjs Scope,我有一个表单如下所示: <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="d

我有一个表单如下所示:

<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>

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

您可以在控制器内指定方法-
$scope.saveDeployment=function(){//这里您可以根据请求生成并处理错误…}
您将把错误保存到scope,然后创建一个指令来监视
$scope.yourResponseObject
,并根据它设置有效性

另外,如果您需要输入字段blur上的request和error之类的内容,则需要使用
elem.bind('blur',…)
创建一个简单指令,在该指令中调用
$scope.saveDeployment
,并使用回调处理有效性


看看这些例子,可能有类似的东西-

我的解决方案是从Kosmetika的想法中提取的

我使用了
angular ui
项目,并在控制器上设置了一个
onBlur
回调,该控制器通过
$http
调用web服务

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


然后,我使用
使用
ng show
查看控制器/模型属性,这样当web服务返回时,它将显示用户信息

使用异步
$http
ajax调用验证表单输入字段是一种常见的需要,但我还没有找到任何完整的、可重用的实现,而且使用方便,所以我尽我最大的努力做了一个

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

我的解决方案具有以下功能:

  • 接受来自
    $scope
    的“check”函数,该函数进行
    $http
    调用或任何类型的验证(同步或异步)
  • $scope
    接受一个“门”函数,该函数允许根据值或
    ngModel
    状态绕过检查
  • 取消“check”函数执行的公告,直到用户停止键入
  • 确保只使用最新的
    $http
    调用结果(以防触发多个调用并按顺序返回)
  • 允许状态绑定,以便UI能够适当方便地响应
  • 可定制的去盎司时间、检查/选通功能、绑定名称和验证名称
我的指示是。它可以用于任何异步验证。我早在1.1.5版本就已经在多个版本中对其进行了测试

下面是一个使用Twitter引导的示例,我在其中检查用户名是否唯一

如果我做了任何改进,它们将是最新的,但我也将把这个指令及其依赖项的代码放在这里(可能不会更新)。不过,我欢迎提出建议或问题


在saveDeployment中执行$http.post请求,如果失败,则向用户显示错误。每次按键都这么做不是很有说服力,我真的不明白为什么你不能/不想在SaveDeployment中这么做。我不知道是否有更好的方法。另外,如何从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;
                });

            }
        };
    });
<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;
};
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;

  }
])

;