# Reactive Forms # 响应式表单 _Reactive forms_ is an Angular technique for creating forms in a _reactive_ style. This guide explains reactive forms as you follow the steps to build a "Hero Detail Editor" form. *响应式表单*是 Angular 中用*响应式*风格创建表单的技术。 本章会在构建“英雄详情编辑器”的过程中,逐步讲解响应式表单的概念。 {@a toc} Try the Reactive Forms live-example. 试试响应式表单的在线例子。 You can also run the Reactive Forms Demo version and choose one of the intermediate steps from the "demo picker" at the top. 你还可以运行响应式表单的演示程序,并从顶部选取一个中间步骤。 {@a intro} ## Introduction to Reactive Forms ## 响应式表单简介 Angular offers two form-building technologies: _reactive_ forms and _template-driven_ forms. The two technologies belong to the `@angular/forms` library and share a common set of form control classes. Angular 提供了两种构建表单的技术:*响应式*表单和*模板驱动*表单。 这两项技术都属于 `@angular/forms` 库,并且共享一组公共的表单控件类。 But they diverge markedly in philosophy, programming style, and technique. They even have their own modules: the `ReactiveFormsModule` and the `FormsModule`. 但是它们在设计哲学、编程风格和具体技术上有显著区别。 所以,它们都有自己的模块:`ReactiveFormsModule` 和 `FormsModule`。 ### Reactive forms ### 响应式表单 Angular _reactive_ forms facilitate a _reactive style_ of programming that favors explicit management of the data flowing between a non-UI _data model_ (typically retrieved from a server) and a UI-oriented _form model_ that retains the states and values of the HTML controls on screen. Reactive forms offer the ease of using reactive patterns, testing, and validation. Angular 的*响应式*表单能让实现*响应式编程风格*更容易,这种编程风格更倾向于在非 UI 的*数据模型*(通常接收自服务器)之间显式的管理数据流, 并且用一个 UI 导向的*表单模型*来保存屏幕上 HTML 控件的状态和值。 响应式表单可以让使用响应式编程模式、测试和校验变得更容易。 With _reactive_ forms, you create a tree of Angular form control objects in the component class and bind them to native form control elements in the component template, using techniques described in this guide. 使用*响应式*表单,你可以在组件中创建表单控件的对象树,并使用本章中传授的技巧把它们绑定到组件模板中的原生表单控件元素上。 You create and manipulate form control objects directly in the component class. As the component class has immediate access to both the data model and the form control structure, you can push data model values into the form controls and pull user-changed values back out. The component can observe changes in form control state and react to those changes. 你可以在组件类中直接创建和维护表单控件对象。由于组件类可以同时访问数据模型和表单控件结构, 因此你可以把表单模型值的变化推送到表单控件中,并把变化后的值拉取回来。 组件可以监听表单控件状态的变化,并对此做出响应。 One advantage of working with form control objects directly is that value and validity updates are [always synchronous and under your control](guide/reactive-forms#async-vs-sync "Async vs sync"). You won't encounter the timing issues that sometimes plague a template-driven form and reactive forms can be easier to unit test. 直接使用表单控件对象的优点之一是值和有效性状态的更新[总是同步的,并且在你的控制之下](guide/reactive-forms#async-vs-sync "Async vs sync")。 你不会遇到时序问题,这个问题有时在模板驱动表单中会成为灾难。而且响应式表单更容易进行单元测试。 In keeping with the reactive paradigm, the component preserves the immutability of the _data model_, treating it as a pure source of original values. Rather than update the data model directly, the component extracts user changes and forwards them to an external component or service, which does something with them (such as saving them) and returns a new _data model_ to the component that reflects the updated model state. 在响应式编程范式中,组件会负责维护*数据模型*的不可变性,把模型当做纯粹的原始数据源。 组件不会直接更新数据模型,而是把用户的修改提取出来,把它们转发给外部的组件或服务,外部程序才会使用这些进行处理(比如保存它们), 并且给组件返回一个新的*数据模型*,以反映模型状态的变化。 Using reactive form directives does not require you to follow all reactive priniciples, but it does facilitate the reactive programming approach should you choose to use it. 使用响应式表单的指令,并不要求你遵循所有的响应式编程原则,但它能让你更容易使用响应式编程方法,从而更愿意使用它。 ### Template-driven forms ### 模板驱动表单 _Template-driven_ forms, introduced in the [Template guide](guide/forms), take a completely different approach. 在[模板](guide/forms)一章中介绍过的*模板驱动*表单,是一种完全不同的方式。 You place HTML form controls (such as `` and `` 和 `` element for the hero name. 这里创建了一个名叫 `name` 的 `FormControl`。 它将会绑定到模板中的一个 `` 元素,表示英雄的名字。 A `FormControl` constructor accepts three, optional arguments: the initial data value, an array of validators, and an array of async validators. `FormControl` 构造函数接收三个可选参数: 初始值、验证器数组和异步验证器数组。
This simple control doesn't have data or validators. In real apps, most form controls have both. For in-depth information on `Validators`, see the [Form Validation](guide/form-validation) guide. 最简单的控件并不需要数据或验证器,但是在实际应用中,大部分表单控件都会同时具备它们。 要想深入了解 `Validators`,参见[表单验证](guide/form-validation)一章。
{@a create-template} ## Create the template ## 创建模板 Now update the component's template with the following markup. 现在,把组件的模板文件 `src/app/hero-detail.component.html` 修改为如下内容: To let Angular know that this is the input that you want to associate to the `name` `FormControl` in the class, you need `[formControl]="name"` in the template on the ``. 要让 Angular 知道你希望把这个输入框关联到类中的 `FormControl` 型属性 `name`,就要在模板中的 `` 上加一句 `[formControl]="name"`。
Disregard the `form-control` CSS class. It belongs to the Bootstrap CSS library, not Angular, and styles the form but in no way impacts the logic. 请忽略 CSS 类 `form-control`,它属于Bootstrap CSS library而不是 Angular。 它会为表单添加样式,但是对表单的逻辑毫无影响。
{@a import} ## Import the `ReactiveFormsModule` ## 导入 `ReactiveFormsModule` The `HeroDetailComponent` template uses the `formControlName` directive from the `ReactiveFormsModule`. `HeroDetailComponent` 的模板中使用了来自 `ReactiveFormsModule` 的 `formControlName`。 Do the following two things in `app.module.ts`: 在 `app.module.ts` 中做了下面两件事: 1. Use a JavaScript `import` statement to access the `ReactiveFormsModule`. 使用 JavaScript 的 `import` 语句访问 `ReactiveFormsModule` 和 `HeroDetailComponent`。 1. Add `ReactiveFormsModule` to the `AppModule`'s `imports` list. 把 `ReactiveFormsModule` 添加到 `AppModule` 的 `imports` 列表中。 {@a update} ## Display the `HeroDetailComponent` ## 显示 `HeroDetailComponent` Revise the `AppComponent` template so it displays the `HeroDetailComponent`. 修改 `AppComponent` 的模板,以便显示 `HeroDetailComponent`。 {@a essentials} ## Essential form classes ## 基础的表单类 This guide uses four fundamental classes to build a reactive form: 本文使用四个基础类来构建响应式表单:
Class CSS 类 Description 说明
AbstractControl [`AbstractControl`](api/forms/AbstractControl "API Reference: FormControl") is the abstract base class for the three concrete form control classes; `FormControl`, `FormGroup`, and `FormArray`. It provides their common behaviors and properties. [`AbstractControl`](api/forms/AbstractControl "API Reference: FormControl")是这三个具体表单类的抽象基类。 并为它们提供了一些共同的行为和属性。
FormControl [`FormControl`](api/forms/FormControl "API Reference: FormControl") tracks the value and validity status of an individual form control. It corresponds to an HTML form control such as an `` or `` 或 `
FormGroup [`FormGroup`](api/forms/FormGroup "API Reference: FormGroup") tracks the value and validity state of a group of `AbstractControl` instances. The group's properties include its child controls. The top-level form in your component is a `FormGroup`. [`FormGroup`](api/forms/FormGroup "API Reference: FormGroup")用于 跟踪*一组*`AbstractControl` 的实例的值和有效性状态。 该组的属性中包含了它的子控件。 组件中的顶级表单就是一个 `FormGroup`。
FormArray [`FormArray`](api/forms/FormArray "API Reference: FormArray") tracks the value and validity state of a numerically indexed array of `AbstractControl` instances. [`FormArray`](api/forms/FormArray "API Reference: FormArray")用于跟踪 `AbstractControl` 实例组成的有序数组的值和有效性状态。
## Style the app ## 为应用添加样式 To use the bootstrap CSS classes that are in the template HTML of both the `AppComponent` and the `HeroDetailComponent`, add the `bootstrap` CSS stylesheet to the head of `styles.css`: 要在 `AppComponent` 和 `HeroDetailComponent` 的模板中使用 Bootstrap 中的 CSS 类。请把 `bootstrap` 的*CSS 样式表文件*添加到 `style.css` 的头部: Now that everything is wired up, serve the app with: 这些做好之后,启动应用服务器: ng serve The browser should display something like this: 浏览器应该显示成这样:
Single FormControl
{@a formgroup} ## Add a FormGroup ## 添加 FormGroup Usually, if you have multiple `FormControls`, you register them within a parent `FormGroup`. To add a `FormGroup`, add it to the imports section of `hero-detail.component.ts`: 通常,如果有多个 `FormControl`,你要把它们都注册进一个父 `FormGroup` 中。 只要把它添加到 `hero-detail.component.ts` 的 `imports` 区就可以了。 In the class, wrap the `FormControl` in a `FormGroup` called `heroForm` as follows: 在这个类中,把 `FormControl` 包裹进了一个名叫 `heroForm` 的 `FormGroup` 中,代码如下: Now that you've made changes in the class, they need to be reflected in the template. Update `hero-detail.component.html` by replacing it with the following. 现在你改完了这个类,该把它映射到模板中了。把 `hero-detail.component.html` 改成这样: Notice that now the single `` is in a `
` element. 注意,现在单行输入框位于一个 `form` 元素中。 `formGroup` is a reactive form directive that takes an existing `FormGroup` instance and associates it with an HTML element. In this case, it associates the `FormGroup` you saved as `heroForm` with the `` element. `formGroup` 是一个响应式表单的指令,它拿到一个现有 `FormGroup` 实例,并把它关联到一个 HTML 元素上。 这种情况下,它关联到的是 `` 元素上的 `FormGroup` 实例 `heroForm`。 Because the class now has a `FormGroup`, you must update the template syntax for associating the `` with the corresponding `FormControl` in the component class. Without a parent `FormGroup`, `[formControl]="name"` worked earlier because that directive can stand alone, that is, it works without being in a `FormGroup`. With a parent `FormGroup`, the `name` `` needs the syntax `formControlName=name` in order to be associated with the correct `FormControl` in the class. This syntax tells Angular to look for the parent `FormGroup`, in this case `heroForm`, and then _inside_ that group to look for a `FormControl` called `name`. 由于现在有了一个 `FormGroup`,因此你必须修改模板语法来把这个 `` 关联到组件类中对应的 `FormControl` 上。 以前没有父 `FormGroup` 的时候,`[formControl]="name"` 也能正常工作,因为该指令可以独立工作,也就是说,不在 `FormGroup` 中时它也能用。 有了 `FormGroup`,`name` 这个 `` 就需要再添加一个语法 `formControlName=name`,以便让它关联到类中正确的 `FormControl` 上。 这个语法告诉 Angular,查阅父 `FormGroup`(这里是 `heroForm`),然后在这个 `FormGroup` 中查阅一个名叫 `name` 的 `FormControl`。 {@a json} ## Taking a look at the form model ## 表单模型概览 When the user enters data into an ``, the value goes into the **_form model_**. To see the form model, add the following line after the closing `` tag in the `hero-detail.component.html`: 当用户在 `` 中输入数据时,它的值就会进入这个**表单模型**。 要想知道表单模型是什么样的,请在 `hero-detail.component.html` 的 `` 标签紧后面添加如下代码: The `heroForm.value` returns the _form model_. Piping it through the `JsonPipe` renders the model as JSON in the browser: `heroForm.value` 会返回表单模型。 用 `JsonPipe` 管道把这个模型以 JSON 格式渲染到浏览器中。
JSON output
The initial `name` property value is the empty string. Type into the name `` and watch the keystrokes appear in the JSON. 最初的 `name` 属性是个空字符串,在 *name* `` 中输入之后,可以看到这些按键出现在了 JSON 中。 In real life apps, forms get big fast. `FormBuilder` makes form development and maintenance easier. 在真实的应用中,表单很快就会变大。 `FormBuilder` 能让表单开发和维护变得更简单。 {@a formbuilder} ## Introduction to `FormBuilder` ## `FormBuilder` 简介 The `FormBuilder` class helps reduce repetition and clutter by handling details of control creation for you. `FormBuilder` 类能通过处理控件创建的细节问题来帮你减少重复劳动。 To use `FormBuilder`, import it into `hero-detail.component.ts`. You can remove `FormControl`: 要使用 `FormBuilder`,就要先把它导入到 `hero-detail.component.ts` 中。你可以删除 `FormControl`: Use it to refactor the `HeroDetailComponent` into something that's easier to read and write, by following this plan: 遵循下列步骤来用 `FormBuilder` 把 `HeroDetailComponent` 重构得更容易读写: * Explicitly declare the type of the `heroForm` property to be `FormGroup`; you'll initialize it later. 明确把 `heroForm` 属性的类型声明为 `FormGroup`,稍后你会初始化它。 * Inject a `FormBuilder` into the constructor. 把 `FormBuilder` 注入到构造函数中。 * Add a new method that uses the `FormBuilder` to define the `heroForm`; call it `createForm()`. 添加一个名叫 `createForm()` 的新方法,它会用 `FormBuilder` 来定义 `heroForm`。 * Call `createForm()` in the constructor. 在构造函数中调用 `createForm()`。 The revised `HeroDetailComponent` looks like this: 修改过的 `HeroDetailComponent` 代码如下: `FormBuilder.group` is a factory method that creates a `FormGroup`.   `FormBuilder.group` takes an object whose keys and values are `FormControl` names and their definitions. In this example, the `name` control is defined by its initial data value, an empty string. `FormBuilder.group` 是一个用来创建 `FormGroup` 的工厂方法,它接受一个对象,对象的键和值分别是 `FormControl` 的名字和它的定义。 在这个例子中,`name` 控件的初始值是空字符串。 Defining a group of controls in a single object makes your code more compact and readable because you don't have to write repeated `new FormControl(...)` statements. 把一组控件定义在一个单一对象中,可以让你的代码更加紧凑、易读。 因为你不必写一系列重复的 `new FormControl(...)` 语句。 {@a validators} ### `Validators.required` Though this guide doesn't go deeply into validations, here is one example that demonstrates the simplicity of using `Validators.required` in reactive forms. 虽然本章不会深入讲解验证机制,但还是有一个例子来示范如何简单的在响应式表单中使用 `Validators.required`。 First, import the `Validators` symbol. 首先,导入 `Validators` 符号。 To make the `name` `FormControl` required, replace the `name` property in the `FormGroup` with an array. The first item is the initial value for `name`; the second is the required validator, `Validators.required`. 要想让 `name` 这个 `FormControl` 是必须的,请把 `FormGroup` 中的 `name` 属性改为一个数组。第一个条目是 `name` 的初始值,第二个是 `required` 验证器:`Validators.required`。
Reactive validators are simple, composable functions. Configuring validation is different in template-driven forms in that you must wrap validators in a directive. 响应式验证器是一些简单、可组合的函数。 在模板驱动表单中配置验证器有些困难,因为你必须把验证器包装进指令中。
Update the diagnostic message at the bottom of the template to display the form's validity status. 修改模板底部的诊断信息,以显示表单的有效性状态。 The browser displays the following: 浏览器会显示下列内容:
Single FormControl
`Validators.required` is working. The status is `INVALID` because the `` has no value. Type into the `` to see the status change from `INVALID` to `VALID`. `Validators.required` 生效了,但状态还是 `INVALID`,因为输入框中还没有值。 在输入框中输入,就会看到这个状态从 `INVALID` 变成了 `VALID`。 In a real app, you'd replace the diagnosic message with a user-friendly experience. 在真实的应用中,你要把这些诊断信息替换成用户友好的信息。 Using `Validators.required` is optional for the rest of the guide. It remains in each of the following examples with the same configuration. 在本章的其余部分,`Validators.required` 是可有可无的,但在每个与此范例配置相同的范例中都会保留它。 For more on validating Angular forms, see the [Form Validation](guide/form-validation) guide. 要了解 Angular 表单验证器的更多知识,参见[表单验证器](guide/form-validation)一章。 ### More `FormControl`s ### 更多的 `FormControl` This section adds additional `FormControl`s for the address, a super power, and a sidekick. 本节要添加一些 `FormControl`,用来表示住址、一项超能力,和一个副手。 Additionally, the address has a state property. The user will select a state with a `` 框中选择一个州,你会用 `