# 七、使用表单

每个网站最终都会使用某种形式让用户输入数据。Angular 特别容易实现客户端表单验证，以提供即时反馈来改善用户体验。

## 实现基本表单

### 问题

您希望创建一个表单来输入用户详细信息，并在 Angular.js 范围内捕获这些信息。

### 溶液

使用标准的`form`标签和`ng-model`指令实现一个基本形式:

```js
    <body ng-app="MyApp">
    <div ng-controller="User">
    <form ng-submit="submit()" class="form-horizontal" novalidate>
    <label>Firstname</label>
    <input type="text" ng-model="user.firstname"/>
    <label>Lastname</label>
    <input type="text" ng-model="user.lastname"/>
    <label>Age</label>
    <input type="text" ng-model="user.age"/>
    <button class="btn">Submit</button>
    </form>
    </div>
    </body>

```

`novalidate`属性禁用 HTML 5 验证，这是现代浏览器支持的客户端验证。在我们的例子中，我们只希望运行的 Angular.js 验证能够完全控制外观。

控制器将表单数据绑定到您的用户模型，并实现`submit()`功能:

```js
    var app = angular.module("MyApp", []);

    app.controller("User", function($scope) {
    $scope.user = {};
    $scope.wasSubmitted = false;

    $scope.submit = function() {
    $scope.wasSubmitted = true;
    };
    });

```

你可以在 [GitHub](https://github.com/fdietz/recipes-with-angular-js-examples/tree/master/chapter7/recipe1) 上找到完整的例子。

### 讨论

使用表单的最初想法是通过序列化表单数据并将其提交给服务器，以传统方式实现表单。相反，我们使用`ng-model`将表单绑定到我们的模型——这是我们在以前的食谱中已经做了很多的事情。

提交按钮的状态反映在我们的`wasSubmitted`范围变量中，但是实际上没有提交到服务器。Angular.js 表单中的默认行为是防止默认操作，因为我们不想重新加载整个页面。我们希望以特定于应用程序的方式处理提交。事实上，背景中还有更多的事情在发生。我们将在下一个食谱中研究`form`或`ng-form`指令的行为。

## 验证表单模型客户端

### 问题

您希望使用 HTML 5 表单属性在客户端验证表单。

### 溶液

Angular.js 与 HTML 5 表单属性协同工作。让我们从相同的表单开始，但是让我们添加一些 HTML 5 属性，使输入成为必需的:

```js
    <form name="form" ng-submit="submit()">
    <label>Firstname</label>
    <input name="firstname" type="text" ng-model="user.firstname" required/>
    <label>Lastname</label>
    <input type="text" ng-model="user.lastname" required/>
    <label>Age</label>
    <input type="text" ng-model="user.age"/>
    <br>
    <button class="btn">Submit</button>
    </form>

```

还是同一个表单，不过这次我们在表单上定义了`name`属性，输入`required`作为名字。

让我们在表单下面添加更多调试输出:

```js
    Firstname input valid: {{form.firstname.$valid}}
    <br>
    Firstname validation error: {{form.firstname.$error}}
    <br>
    Form valid: {{form.$valid}}
    <br>
    Form validation error: {{form.$error}}

```

你可以在 [GitHub](https://github.com/fdietz/recipes-with-angular-js-examples/tree/master/chapter7/recipe2) 上找到完整的例子。

### 讨论

当从一个新的空表单开始时，您会注意到 Angular 将 CSS 类`ng-pristine`和`ng-valid`添加到表单标签和每个输入标签中。编辑表单时，`ng-pristine`类将从已更改的输入字段和表单标签中删除。将被`ng-dirty`班取代。这非常有用，因为它允许您根据这些状态轻松地向应用程序添加新功能。

除了这两个 CSS 类之外，还有两个需要研究。只要输入有效，就会增加`ng-valid`类；否则增加 CSS 类`ng-invalid`。请注意，根据输入字段的不同，表单标记也会获得有效或无效的类。为了演示这一点，我添加了`required` HTML 5 属性。最初，名字和姓氏输入字段为空，因此具有`ng-invalid` CSS 类，而年龄输入字段具有`ng-valid`类。此外，在`ng-invalid`旁边还有`ng-invalid-required`级，以获得更高的特异性。

由于我们在表单 HTML 元素上定义了`name`属性，我们现在可以通过范围变量访问 Angular 的表单控制器。在调试输出中，我们可以检查每个命名表单输入和表单本身的有效性和特定错误。请注意，这仅适用于表单名称属性级别，而不适用于模型范围。如果输出下面的表达式`{{user.firstname.$error}},`就不行了。

Angular 的表单控制器暴露了`$valid`、`$invalid`、`$error`、`$pristine,`和`$dirty`变量。

对于验证，Angular 提供内置指令，包括`required`、`pattern`、`minlength`、`maxlength`、`min,`和`max`。

让我们使用 Angular 的表单集成来实际显示下一个配方中的验证错误。

## 显示表单验证错误

### 问题

您希望通过将输入字段标记为红色并显示错误消息来向用户显示验证错误。

### 溶液

如果表单输入无效，我们可以使用`ng-show`指令显示错误消息，使用 CSS 类根据输入元素的状态更改其背景颜色。

让我们从样式变化开始:

```js
    <style type="text/css">
    input.ng-invalid.ng-dirty {
    background-color: red;
    }
    input.ng-valid.ng-dirty {
    background-color: green;
    }
    </style>

```

这是表单的一小部分，输入字段有一条错误消息:

```js
    <label>Firstname</label>
    <input name="firstname" type="text" ng-model="user.firstname" required/>
    <p ng-show="form.firstname.$invalid && form.firstname.$dirty">
    Firstname is required
    </p>

```

你可以在 [GitHub](https://github.com/fdietz/recipes-with-angular-js-examples/tree/master/chapter7/recipe3) 上找到完整的例子。

### 讨论

CSS 类确保我们最初显示的是没有任何类的新表单。当用户第一次开始键入一些输入时，我们将其更改为绿色或红色。这是使用`ng-dirty`和`ng-invalid` CSS 类的一个很好的例子。

我们在`ng-show`指令中使用相同的逻辑，只在用户第一次开始打字时显示错误信息。

## 使用 Twitter 引导框架显示表单验证错误

### 问题

您希望显示表单验证错误，但是表单是使用[推特引导](http://twitter.github.com/bootstrap/index.html)设计的。

### 溶液

使用`.horizontal-form`类时，Twitter Bootstrap 使用`div`元素将标签、输入字段和帮助消息组织成组。组 div 有类`control-group`，实际的控件进一步嵌套在另一个带有 CSS 类`controls`的`div`元素中。当在 div 上添加 CSS 类`error`和`control-group`类时，推特引导显示了一个很好的验证状态。

让我们从表单开始:

```js
    <div ng-controller="User">
    <form name="form" ng-submit="submit()" novalidate>

    <div class="control-group" ng-class="error('firstname')">
    <label class="control-label" for="firstname">Firstname</label>
    <div class="controls">
    <input id="firstname" name="firstname" type="text"
    ng-model="user.firstname" placeholder="Firstname" required/>
    <span class="help-block"
    ng-show="form.firstname.$invalid && form.firstname.$dirty">
    First Name is required
    </span>
    </div>
    </div>

    <div class="control-group" ng-class="error('lastname')">
    <label class="control-label" for="lastname">Lastname</label>
    <div class="controls">
    <input id="lastname" name="lastname" type="text"
    ng-model="user.lastname" placeholder="Lastname" required/>
    <span class="help-block"
    ng-show="form.lastname.$invalid && form.lastname.$dirty">
    Last Name is required
    </span>
    </div>
    </div>

    <div class="control-group">
    <div class="controls">
    <button ng-disabled="form.$invalid" class="btn">Submit</button>
    </div>
    </div>
    </form>
    </div>

```

请注意，我们在`control-group` div 上使用`ng-class`指令。那么，让我们看看控制器实现的`error`功能:

```js
    app.controller("User", function($scope) {
    // ...
    $scope.error = function(name) {
    var s = $scope.form[name];
    return s.$invalid && s.$dirty ? "error" : "";
    };
    });

```

错误函数获取作为字符串传递的输入名称属性，并检查`$invalid`和`$dirty`标志以返回错误类或空字符串。

你可以在 [GitHub](https://github.com/fdietz/recipes-with-angular-js-examples/tree/master/chapter7/recipe4) 上找到完整的例子。

### 讨论

同样，我们检查无效和脏标志，因为我们只在用户实际更改表单的情况下显示错误消息。注意这个`ng-class`函数的用法在 Angular 中非常典型，因为表达式不支持三进制检查。

## 仅在表单有效时启用提交按钮

### 问题

只要表单包含无效数据，您就希望禁用“提交”按钮。

### 溶液

结合`ng-disabled`指令使用`$form.invalid`状态。

这是更改后的提交按钮:

```js
    <button ng-disabled="form.$invalid" class="btn">Submit</button>

```

你可以在 [GitHub](https://github.com/fdietz/recipes-with-angular-js-examples/tree/master/chapter7/recipe5) 上找到完整的例子。

### 讨论

表单控制器属性`form.$invalid`和朋友非常有用，可以覆盖各种关注表单整体而不是单个字段的用例。

请注意，您必须为表单元素分配一个`name`属性，否则`form.$invalid`将不可用。

## 实施自定义验证

### 问题

您希望通过将其与单词黑名单进行比较来验证用户输入。

### 溶液

[angular-ui](http://angular-ui.github.com/) 项目提供了一个很好的自定义验证指令，允许您通过表达式传递选项。

让我们先看看模板，使用`ui-validate`指令:

```js
    <input name="firstname" type="text"
    ng-model="user.firstname" required
    ui-validate=" { blacklisted: 'notBlacklisted($value)' } "
    />

    <p ng-show='form.firstname.$error.blackListed'>
    This firstname is blacklisted.
    </p>

```

而控制器用`notBlackListed`实现:

```js
    var app = angular.module("MyApp", ["ui", "ui.directives"]);

    app.controller("User", function($scope) {
    $scope.blacklist = ['idiot','loser'];

    $scope.notBlackListed = function(value) {
    return $scope.blacklist.indexOf(value) === -1;
    };
    });

```

你可以在 [GitHub](https://github.com/fdietz/recipes-with-angular-js-examples/tree/master/chapter7/recipe6) 上找到完整的例子。

### 讨论

首先，我们需要明确列出我们对 Angular UI 指令模块的模块依赖。确保您确实下载了 JavaScript 文件，并通过脚本标签加载了它。

我们的黑名单包含我们不想接受作为用户输入的单词，并且`notBlackListed`功能检查用户输入是否匹配黑名单中定义的任何单词。

`ui-validate`指令非常强大，因为它允许您通过在控制器函数中实现业务逻辑来轻松定义自定义验证。

如果你想知道更多，看看如何在 Angular 的优秀[指南](http://docs.angularjs.org/guide/forms)中自己实现自定义指令。