57

我试图了解如何做以下事情:

声明表格的公认方式是什么。我的理解是您只需在 HTML 中声明表单,然后添加 ng-model 指令,如下所示:

ng-model="item.name"

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

我真的需要一个例子,但是 Angulars 文档很难理解。

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

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

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

{
  ...data...

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

然后绑定到 DOM。

4

9 回答 9

64

我最近也一直在玩这种东西,我敲了这个演示。我认为它可以满足您的需求。

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

<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 为您获取表单。(如果您想了解表单控制器的工作原理,请在此处查看表单控制器的源代码)。

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

$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]);

希望有帮助

于 2013-05-10T08:30:08.863 回答
17

我有与 Derek 类似的解决方案,在codetunes 博客上有所描述。TL;博士:

  • 以与 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(', ') 
    

希望它会有用:)

于 2013-09-30T11:50:03.060 回答
5

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

于 2013-06-27T22:21:47.683 回答
3

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

    });
};

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

于 2013-04-23T12:26:18.690 回答
3

如果你选择 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 才能进入失败回调。

于 2013-04-23T14:40:41.183 回答
3

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

Angular 1.3 表单验证教程

参考:

于 2015-06-02T05:25:38.000 回答
2

我在一些项目中需要这个,所以我创建了一个指令。最后花了一点时间把它放在 GitHub 上,供任何想要一个插入式解决方案的人使用。

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

特征:

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

  • 与 Angulars 内置验证一起使用,并且可以通过 formName.inputName.$error.ngRemoteValidate 访问 cab

  • 限制服务器请求(默认 400 毫秒)并且可以设置为ng-remote-throttle="550"

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

    更改密码

    当前 必需 当前密码不正确。请输入您当前的帐户密码。
    <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>
    
于 2014-04-24T23:11:33.733 回答
1

作为变体

// 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');
  }
}
于 2015-06-18T10:59:08.110 回答
0

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

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

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

你可以在这里试验一下。您可能还想检查一下ngMessages。还有几篇相关的 文章

于 2021-11-27T15:47:40.543 回答