【问题标题】:AngularJS - Server side validation and client side formsAngularJS - 服务器端验证和客户端表单
【发布时间】:2013-04-16 14:36:58
【问题描述】:

我正在尝试了解如何执行以下操作:

声明表单的公认方式是什么。我的理解是你只需在 HTML 中声明表单,然后像这样添加 ng-model 指令:

ng-model="item.name"

发送到服务器的内容。我可以将项目对象作为 JSON 发送到服务器,并对其进行解释。然后我可以对对象执行验证。如果失败,我会抛出一个 JSON 错误,并返回究竟是什么?有没有一种公认的方式来做到这一点?如何以一种不错的方式将验证错误从服务器推送到客户端?

我确实需要一个示例,但 Angulars 文档很难理解。

编辑:看来我的问题表述得很糟糕。

我知道如何验证客户端,以及如何将错误/成功作为 promise 回调来处理。我想知道的是,将服务器端错误消息捆绑到客户端的公认方式。假设我有一个用户名和密码注册表单。我不想轮询服务器以获取用户名,然后使用 Angular 来确定是否存在重复。我想将用户名发送到服务器,验证不存在同名的其他帐户,然后提交表单。如果发生错误,我该如何发回?

如何将数据按原样(键和值)推送到服务器,并附加一个错误字段,如下所示:

{
  ...data...

  "errors": [
    {
      "context": null,
      "message": "A detailed error message.",
      "exceptionName": null
    }
  ]
}

然后绑定到 DOM。

【问题讨论】:

标签: json angularjs


【解决方案1】:

默认情况下,表单是正常提交​​的。如果您没有为表单中的每个字段提供名称属性,那么它将不会提交正确的数据。您可以做的是在提交表单之前捕获表单,然后自己通过 ajax 提交该数据。

<form ng-submit="onSubmit(); return false">

然后在你的$scope.onSubmit() 函数中:

$scope.onSubmit = function() {
  var data = {
    'name' : $scope.item.name
  };
  $http.post(url, data)
    .success(function() {
    })
    .failure(function() {

    });
};

您还可以通过设置所需属性来验证数据。

【讨论】:

  • @mastko。我看到了你关于在 AngularJs 1.3 中驯服表单的教程,我真的很喜欢它,但是关于我的实现我有几个问题。如果可能,我想通过电子邮件讨论它们。
  • @Naomi 请通过我的网站与我联系。
  • 我想我通过 twitter 尝试过,没有看到其他的交流方式。不过,我能够解决我的问题,所以现在一切都很顺利。我结合了我在网上看到的几个想法,最终我只能在表单发生变化而不是加载时运行验证器。
  • 以防万一这对您有所帮助,我写了一篇关于我的解决方案的文章。这是链接social.technet.microsoft.com/wiki/contents/articles/…
【解决方案2】:

如果你选择 ngResource,它看起来像这样

var Item = $resource('/items/');
$scope.item = new Item();
$scope.submit = function(){
  $scope.item.$save(
    function(data) {
        //Yahooooo :)
    }, function(response) {
        //oh noooo :(
        //I'm not sure, but your custom json Response should be stick in response.data, just inspect the response object 
    }
  );
};

最重要的是,您的 HTTP-Response 代码必须是 4xx 才能进入失败回调。

【讨论】:

    【解决方案3】:

    我最近也在玩这种东西,我敲了this demo。我认为它可以满足您的需求。

    使用您要使用的任何特定客户端验证正常设置您的表单:

    <div ng-controller="MyCtrl">
        <form name="myForm" onsubmit="return false;">
            <div>
                <input type="text" placeholder="First name" name="firstName" ng-model="firstName" required="true" />
                <span ng-show="myForm.firstName.$dirty && myForm.firstName.$error.required">You must enter a value here</span>
                <span ng-show="myForm.firstName.$error.serverMessage">{{myForm.firstName.$error.serverMessage}}</span>
            </div>
            <div>
                <input type="text" placeholder="Last name" name="lastName" ng-model="lastName"/>
                <span ng-show="myForm.lastName.$error.serverMessage">{{myForm.lastName.$error.serverMessage}}</span>
            </div>
            <button ng-click="submit()">Submit</button>
        </form>
    </div>
    

    另外请注意,我为每个字段添加了serverMessage

    <span ng-show="myForm.firstName.$error.serverMessage">{{myForm.firstName.$error.serverMessage}}</span>
    

    这是一条从服务器返回的可自定义消息,它的工作方式与任何其他错误消息相同(据我所知)。

    这里是控制器:

    function MyCtrl($scope, $parse) {
          var pretendThisIsOnTheServerAndCalledViaAjax = function(){
              var fieldState = {firstName: 'VALID', lastName: 'VALID'};
              var allowedNames = ['Bob', 'Jill', 'Murray', 'Sally'];
    
              if (allowedNames.indexOf($scope.firstName) == -1) fieldState.firstName = 'Allowed values are: ' + allowedNames.join(',');
              if ($scope.lastName == $scope.firstName) fieldState.lastName = 'Your last name must be different from your first name';
    
              return fieldState;
          };
          $scope.submit = function(){
              var serverResponse = pretendThisIsOnTheServerAndCalledViaAjax();
    
              for (var fieldName in serverResponse) {
                  var message = serverResponse[fieldName];
                  var serverMessage = $parse('myForm.'+fieldName+'.$error.serverMessage');
    
                  if (message == 'VALID') {
                      $scope.myForm.$setValidity(fieldName, true, $scope.myForm);
                      serverMessage.assign($scope, undefined);
                  }
                  else {
                      $scope.myForm.$setValidity(fieldName, false, $scope.myForm);
                      serverMessage.assign($scope, serverResponse[fieldName]);
                  }
              }
          };
    }
    

    我假装在pretendThisIsOnTheServerAndCalledViaAjax 调用服务器,你可以用ajax 调用替换它,关键是它只是返回每个字段的验证状态。在这个简单的例子中,我使用值VALID 来指示该字段是有效的,任何其他值都被视为错误消息。您可能想要更复杂的东西!

    从服务器获得验证状态后,您只需更新表单中的状态。

    您可以从范围访问表单,在这种情况下,表单称为myForm,因此 $scope.myForm 会为您获取表单。 (form controller is here 的来源,如果您想了解它的工作原理)。

    然后你想告诉表单该字段是否有效/无效:

    $scope.myForm.$setValidity(fieldName, true, $scope.myForm);
    

    $scope.myForm.$setValidity(fieldName, false, $scope.myForm);
    

    我们还需要设置错误信息。首先使用 $parse 获取字段的访问器。然后从服务器分配值。

    var serverMessage = $parse('myForm.'+fieldName+'.$error.serverMessage');
    serverMessage.assign($scope, serverResponse[fieldName]);
    

    希望有帮助

    【讨论】:

    • 您可以使用 ng-submit 代替 ng-click 和 onsubmit 解决方法。
    • 不错的解决方案。但是有两个评论:1)自动化流程会很棒,所以我不必将那些“span ng-show”放在客户端和服务器错误的每个字段下; 2)服务器实际上可能会为同一字段发送多个错误,我想显示所有错误,因此如果每次尝试填写该字段都会导致服务器发出新的抱怨,用户不必诅咒我; 3)不确定在哪些情况下需要 $dirty - 如果用户更改字段值,也许我们应该清除服务器错误?简而言之 - 一个可配置的解决方案(Angular 插件)会很棒。
    • JustAMartin,这只是一些示例代码,不是完整的实现。您将需要自己进一步实施任何事情。随时发布您的附加答案。
    • 使用$scope.myForm.$setValidity(fieldName, false/true, $scope.myForm) 不会在true/false 状态下设置$scope.myForm.fieldName.$invalid(只有表单会这样)。要解决此问题,您还必须设置字段$scope.myForm[fieldName].$setValidity(fieldName, true/false);
    【解决方案4】:

    嗯,Derek Ekins 给出的答案非常好用。但是:如果您使用ng-disabled="myForm.$invalid" 禁用提交按钮 - 该按钮不会自动返回启用状态,因为基于服务器的错误状态似乎没有改变。即使您再次编辑表单中的所有字段以符合有效输入(基于客户端验证),也不会。

    【讨论】:

    • 如果您希望能够禁用提交按钮,那么您需要在输入更改时触发验证,否则无法知道它是否有效。如果你真的想要,我可以举个例子。
    • 是的,我想验证输入的条形码唯一性。我喜欢你的实现,顺便说一句,它确实有很大帮助。
    【解决方案5】:

    我有与 Derek 类似的解决方案,在 codetunes blog 上进行了描述。 TL;DR:

    • 以与 Derek 的解决方案类似的方式显示错误:

      <span ng-show="myForm.fieldName.$error.server">{{errors.fieldName}}</span>
      

    • 添加指令,当用户更改输入时清除错误:

      <input type="text" name="fieldName" ng-model="model.fieldName" server-error />
      
      angular.module('app').directive 'serverError', ->
        {
          restrict: 'A'
          require: '?ngModel'
          link: (scope, element, attrs, ctrl) ->
            element.on 'change', ->
              scope.$apply ->
                ctrl.$setValidity('server', true)
        }
      
    • 通过将错误消息传递给范围并告诉表单有错误来处理错误:

      errorCallback = (result) ->
        # server will return something like:
        # { errors: { name: ["Must be unique"] } }
        angular.forEach result.data.errors, (errors, field) ->
          # tell the form that field is invalid
          $scope.form[field].$setValidity('server', false)
          # keep the error messages from the server
          $scope.errors[field] = errors.join(', ') 
      

    希望对你有用:)

    【讨论】:

    • 这适用于 angular-1.3/1.4 吗? $scope.form 的值似乎是未定义的,即使已命名表单并且当我尝试使用表单或表单名称访问时,我发现它未定义。
    • @DivKis01 您是否在表单中添加了 name="form"?来自docs:“表单的名称。如果指定,表单控制器将在此名称下发布到相关范围内。”
    • 是的,我确实添加了 name="form"
    • 只有在用户离开字段时才重置有效性。
    【解决方案6】:

    我在几个项目中需要这个,所以我创建了一个指令。终于花点时间把它放在 GitHub 上,供任何想要直接解决方案的人使用。

    https://github.com/webadvanced/ng-remote-validate

    特点:

    • Ajax 验证任何文本或密码输入的解决方案

    • 使用 Angulars 内置验证并通过 formName.inputName.$error.ngRemoteValidate 访问 cab

    • 限制服务器请求(默认 400 毫秒),可以使用 ng-remote-throttle="550" 进行设置

    • 允许使用ng-remote-method="GET" 定义 HTTP 方法(默认 POST) 要求用户输入当前密码和新密码的更改密码表单的示例用法。:

      更改密码

      当前的 必需的 当前密码不正确。请输入您当前的帐户密码。
      <label for="newPassword">New</label>
      <input type="password"
             name="newPassword"
             placeholder="New password"
             ng-model="password.new"
             required>
      
      <label for="confirmPassword">Confirm</label>
      <input ng-disabled=""
             type="password"
             name="confirmPassword"
             placeholder="Confirm password"
             ng-model="password.confirm"
             ng-match="password.new"
             required>
      <span ng-show="changePasswordForm.confirmPassword.$error.match">
          New and confirm do not match
      </span>
      
      <div>
          <button type="submit" 
                  ng-disabled="changePasswordForm.$invalid" 
                  ng-click="changePassword(password.new, changePasswordForm);reset();">
              Change password
          </button>
      </div>
      

    【讨论】:

      【解决方案7】:

      截至 2014 年 7 月,AngularJS 1.3 添加了新的表单验证功能。这包括 ngMessages 和 asyncValidators,因此您现在可以在提交表单之前触发每个字段的服务器端验证。

      Angular 1.3 表单验证教程

      参考资料:

      【讨论】:

      • 谢谢,托尼。打算研究一下,看看我是否可以实施需要对输入进行服务器端测试的重复预防。
      • 这似乎是一个很好的教程,我希望能够遵循,但我已经有一个快速的问题。我如何与这个博客的作者交流我的问题?我的问题是 - 我想从服务器返回一条错误消息。我应该如何实现呢?
      • 您很可能希望使用asyn validator
      • 你还想看看latest docs for Angular validation,因为自 Angular 1.x 以来发生了很多变化
      • 这是Angular 2 Form Validation的新教程
      【解决方案8】:

      作为变体

      // ES6 form controller class
      
      class FormCtrl {
       constructor($scope, SomeApiService) {
         this.$scope = $scope;
         this.someApiService = SomeApiService;
         this.formData = {};
       }
      
       submit(form) {
         if (form.$valid) {
           this.someApiService
               .save(this.formData)
               .then(() => {
                 // handle success
      
                 // reset form
                 form.$setPristine();
                 form.$setUntouched();
      
                 // clear data
                 this.formData = {};
               })
               .catch((result) => {
                 // handle error
                 if (result.status === 400) {
                   this.handleServerValidationErrors(form, result.data && result.data.errors)
                 } else {// TODO: handle other errors}
               })
         }
       }
      
       handleServerValidationErrors(form, errors) {
        // form field to model map
        // add fields with input name different from name in model
        // example: <input type="text" name="bCategory" ng-model="user.categoryId"/>
        var map = {
          categoryId: 'bCategory',
          // other
        };
      
        if (errors && errors.length) {
          // handle form fields errors separately
          angular.forEach(errors, (error) => {
            let formFieldName = map[error.field] || error.field;
            let formField = form[formFieldName];
            let formFieldWatcher;
      
            if (formField) {
              // tell the form that field is invalid
              formField.$setValidity('server', false);
      
              // waits for any changes on the input
              // and when they happen it invalidates the server error.
              formFieldWatcher = this.$scope.$watch(() => formField.$viewValue, (newValue, oldValue) => {
                if (newValue === oldValue) {
                  return;
                }
      
                // clean up the server error
                formField.$setValidity('server', true);
      
                // clean up form field watcher
                if (formFieldWatcher) {
                  formFieldWatcher();
                  formFieldWatcher = null;
                }
              });
            }
          });
      
        } else {
          // TODO: handle form validation
          alert('Invalid form data');
        }
      }
      

      【讨论】:

        【解决方案9】:

        据我了解,问题是关于将错误从服务器传递到客户端。我不确定是否有成熟的做法。所以我将描述一种可能的方法:

        <form name="someForm" ng-submit="submit()" ng-controller="c1" novalidate>
            <input name="someField" type="text" ng-model="data.someField" required>
            <div ng-show="someForm.$submitted || someForm.someField.$touched">
                <div ng-show="someForm.someField.$error.required" class="error">required</div>
                <div ng-show="someForm.someField.$error.someError" class="error">some error</div>
            </div>
            <input type="submit">
        </form>
        

        假设服务器返回以下类型的对象:

        {errors: {
            someField: ['someError'],
        }}
        

        然后您可以通过这种方式将错误传递给 UI:

        Object.keys(resp.errors).forEach(i => {
            resp.errors[i].forEach(c => {
                $scope.someForm[i].$setValidity(c, false);
                $scope.someForm[i].$validators.someErrorResetter
                    = () => $scope.someForm[i].$setValidity(c, true);
            });
        });
        

        我使每个字段无效并添加一个验证器(它不是真正的验证器)。由于每次更改后都会调用验证器,因此让我们重置错误状态。

        您可以尝试一下here。您可能还想查看ngMessages。还有几个relatedarticles

        【讨论】:

          猜你喜欢
          • 2012-12-19
          • 2011-10-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-10-16
          • 1970-01-01
          相关资源
          最近更新 更多