# Forms # 表单 Forms are the mainstay of business applications. You use forms to log in, submit a help request, place an order, book a flight, schedule a meeting, and perform countless other data-entry tasks. 表单是商业应用的支柱,你用它来执行登录、求助、下单、预订机票、安排会议,以及不计其数的其它数据录入任务。 In developing a form, it's important to create a data-entry experience that guides the user efficiently and effectively through the workflow. 在开发表单时,创建数据方面的体验是非常重要的,它能指引用户明细、高效的完成工作流程。 Developing forms requires design skills (which are out of scope for this page), as well as framework support for *two-way data binding, change tracking, validation, and error handling*, which you'll learn about on this page. 开发表单需要设计能力(那超出了本章的范围),而框架支持*双向数据绑定、变更检测、验证和错误处理*,而本章你将会学到它们。 This page shows you how to build a simple form from scratch. Along the way you'll learn how to: 这个页面演示了如何从草稿构建一个简单的表单。这个过程中你将学会如何: * Build an Angular form with a component and template. 用组件和模板构建 Angular 表单 * Use `ngModel` to create two-way data bindings for reading and writing input-control values. 用 `ngModel` 创建双向数据绑定,以读取和写入输入控件的值 * Track state changes and the validity of form controls. 跟踪状态的变化,并验证表单控件 * Provide visual feedback using special CSS classes that track the state of the controls. 使用特殊的 CSS 类来跟踪控件的状态并给出视觉反馈 * Display validation errors to users and enable/disable form controls. 向用户显示验证错误提示,以及启用/禁用表单控件 * Share information across HTML elements using template reference variables. 使用模板引用变量在 HTML 元素之间共享信息 You can run the in Stackblitz and download the code from there. 运行来试用本页的代码。 {@a template-driven} ## Template-driven forms ## 模板驱动表单 (template-driven forms) You can build forms by writing templates in the Angular [template syntax](guide/template-syntax) with the form-specific directives and techniques described in this page. 通常,使用 Angular [模板语法](guide/template-syntax)编写模板,结合本章所描述的表单专用指令和技术来构建表单。
You can also use a reactive (or model-driven) approach to build forms. However, this page focuses on template-driven forms. 你还可以使用响应式(也叫模型驱动)的方式来构建表单。不过本章中只介绍模板驱动表单。
You can build almost any form with an Angular template—login forms, contact forms, and pretty much any business form. You can lay out the controls creatively, bind them to data, specify validation rules and display validation errors, conditionally enable or disable specific controls, trigger built-in visual feedback, and much more. 利用 Angular 模板,可以构建几乎所有表单 — 登录表单、联系人表单…… 以及任何的商务表单。 可以创造性的摆放各种控件、把它们绑定到数据、指定校验规则、显示校验错误、有条件的禁用或 启用特定的控件、触发内置的视觉反馈等等,不胜枚举。 Angular makes the process easy by handling many of the repetitive, boilerplate tasks you'd otherwise wrestle with yourself. 它用起来很简单,这是因为 Angular 处理了大多数重复、单调的任务,这让你可以不必亲自操刀、身陷其中。 You'll learn to build a template-driven form that looks like this: 你将学习构建如下的“模板驱动”表单:
Clean Form
The *Hero Employment Agency* uses this form to maintain personal information about heroes. Every hero needs a job. It's the company mission to match the right hero with the right crisis. 这里是*英雄职业介绍所*,使用这个表单来维护候选英雄们的个人信息。每个英雄都需要一份工作。 公司的使命就是让合适的英雄去应对恰当的危机! Two of the three fields on this form are required. Required fields have a green bar on the left to make them easy to spot. 表单中的三个字段,其中两个是必填的。必填的字段在左侧有个绿色的竖条,方便用户分辨哪些是必填项。 If you delete the hero name, the form displays a validation error in an attention-grabbing style: 如果删除了英雄的名字,表单就会用醒目的样式把验证错误显示出来。
Invalid, Name Required
Note that the *Submit* button is disabled, and the "required" bar to the left of the input control changes from green to red. 注意,提交按钮被禁用了,而且输入控件左侧的“必填”条从绿色变为了红色。
You can customize the colors and location of the "required" bar with standard CSS. 稍后,会使用标准 CSS 来定制“必填”条的颜色和位置。
You'll build this form in small steps: 你将一点点构建出此表单: 1. Create the `Hero` model class. 创建 `Hero` 模型类 1. Create the component that controls the form. 创建控制此表单的组件。 1. Create a template with the initial form layout. 创建具有初始表单布局的模板。 1. Bind data properties to each form control using the `ngModel` two-way data-binding syntax. 使用 `ngModel` 双向数据绑定语法把数据属性绑定到每个表单输入控件。 1. Add a `name` attribute to each form-input control. 往每个表单输入控件上添加 `name` 属性 (attribute)。 1. Add custom CSS to provide visual feedback. 添加自定义 CSS 来提供视觉反馈。 1. Show and hide validation-error messages. 显示和隐藏有效性验证的错误信息。 1. Handle form submission with *ngSubmit*. 使用 **ngSubmit** 处理表单提交。 1. Disable the form’s *Submit* button until the form is valid. 禁用此表单的提交按钮,直到表单变为有效。 ## Setup ## 准备工作 Create a new project named angular-forms: 创建一个名为 angular-forms 的新项目: ng new angular-forms ## Create the Hero model class ## 创建 Hero 模型类 As users enter form data, you'll capture their changes and update an instance of a model. You can't lay out the form until you know what the model looks like. 当用户输入表单数据时,需要捕获它们的变化,并更新到模型的实例中。 除非知道模型里有什么,否则无法设计表单的布局。 A model can be as simple as a "property bag" that holds facts about a thing of application importance. That describes well the `Hero` class with its three required fields (`id`, `name`, `power`) and one optional field (`alterEgo`). 最简单的模型是个“属性包”,用来存放应用中一件事物的事实。 这里使用三个必备字段 (`id`、`name`、`power`),和一个可选字段 (`alterEgo`,译注:中文含义是第二人格,例如 X 战警中的 Jean / 黑凤凰)。 Using the Angular CLI, generate a new class named `Hero`: 使用 Angular CLI 生成一个名叫 `Hero` 的新类: ng generate class Hero With this content: 内容如下: It's an anemic model with few requirements and no behavior. Perfect for the demo. 这是一个少量需求和零行为的贫血模型。对演示来说很完美。 The TypeScript compiler generates a public field for each `public` constructor parameter and automatically assigns the parameter’s value to that field when you create heroes. TypeScript 编译器为每个 `public` 构造函数参数生成一个公共字段,在创建新的英雄实例时,自动把参数值赋给这些公共字段。 The `alterEgo` is optional, so the constructor lets you omit it; note the question mark (?) in `alterEgo?`. `alterEgo` 是可选的,调用构造函数时可省略,注意 `alterEgo?` 中的问号 (?)。 You can create a new hero like this: 可以这样创建新英雄: ## Create a form component ## 创建表单组件 An Angular form has two parts: an HTML-based _template_ and a component _class_ to handle data and user interactions programmatically. Begin with the class because it states, in brief, what the hero editor can do. Angular 表单分为两部分:基于 HTML 的*模板*和组件*类*,用来程序处理数据和用户交互。 先从组件类开始,是因为它可以简要说明英雄编辑器能做什么。 Using the Angular CLI, generate a new component named `HeroForm`: 使用 Angular CLI 生成一个名叫 `HeroForm` 的新组件: ng generate component HeroForm With this content: 内容如下: There’s nothing special about this component, nothing form-specific, nothing to distinguish it from any component you've written before. 这个组件没有什么特别的地方,没有表单相关的东西,与之前写过的组件没什么不同。 Understanding this component requires only the Angular concepts covered in previous pages. 只需要前面章节中学过的概念,就可以完全理解这个组件: * The code imports the Angular core library and the `Hero` model you just created. 这段代码导入了 Angular 核心库以及你刚刚创建的 `Hero` 模型。 * The `@Component` selector value of "hero-form" means you can drop this form in a parent template with a `` tag. `@Component` 选择器“hero-form”表示可以用 `` 标签把这个表单放进父模板。 * The `templateUrl` property points to a separate file for the template HTML. `moduleId: module.id` 属性设置了基地址,用于从相对模块路径加载 `templateUrl`。 * You defined dummy data for `model` and `powers`, as befits a demo. `templateUrl` 属性指向一个独立的 HTML 模板文件。 Down the road, you can inject a data service to get and save real data or perhaps expose these properties as inputs and outputs (see [Input and output properties](guide/template-syntax#inputs-outputs) on the [Template Syntax](guide/template-syntax) page) for binding to a parent component. This is not a concern now and these future changes won't affect the form. 接下来,你可以注入一个数据服务,以获取或保存真实的数据,或者把这些属性暴露为输入属性和输出属性(参见[Template Syntax](guide/template-syntax)中的[输入和输出属性](guide/template-syntax#inputs-outputs))来绑定到一个父组件。这不是现在需要关心的问题,未来的更改不会影响到这个表单。 * You added a `diagnostic` property to return a JSON representation of the model. It'll help you see what you're doing during development; you've left yourself a cleanup note to discard it later. 你添加一个 `diagnostic` 属性,以返回这个模型的 JSON 形式。在开发过程中,它用于调试,最后清理时会丢弃它。 ## Revise *app.module.ts* ## 修改 *app.module.ts* `app.module.ts` defines the application's root module. In it you identify the external modules you'll use in the application and declare the components that belong to this module, such as the `HeroFormComponent`. `app.module.ts` 定义了应用的根模块。其中标识即将用到的外部模块,以及声明属于本模块中的组件,例如 `HeroFormComponent`。 Because template-driven forms are in their own module, you need to add the `FormsModule` to the array of `imports` for the application module before you can use forms. 因为模板驱动的表单位于它们自己的模块,所以在使用表单之前,需要将 `FormsModule` 添加到应用模块的 `imports` 数组中。 Update it with the following: 对它做如下修改:
There are two changes: 有两处更改 1. You import `FormsModule`. 导入 `FormsModule`。 1. You add the `FormsModule` to the list of `imports` defined in the `@NgModule` decorator. This gives the application access to all of the template-driven forms features, including `ngModel`. 把 `FormsModule` 添加到 `ngModule` 装饰器的 `imports` 列表中,这样应用就能访问模板驱动表单的所有特性,包括 `ngModel`。
If a component, directive, or pipe belongs to a module in the `imports` array, ​_don't_​ re-declare it in the `declarations` array. If you wrote it and it should belong to this module, ​_do_​ declare it in the `declarations` array. 如果某个组件、指令或管道是属于 `imports` 中所导入的某个模块的,那就*不能再*把它再声明到本模块的 `declarations` 数组中。 如果它是你自己写的,并且确实属于当前模块,*才应该*把它声明在 `declarations` 数组中。
## Revise *app.component.html* ## 修改 *app.component.ts* `AppComponent` is the application's root component. It will host the new `HeroFormComponent`. `AppComponent` 是应用的根组件,`HeroFormComponent` 将被放在其中。 Replace the contents of its template with the following: 把模板中的内容替换成如下代码:
There are only two changes. The `template` is simply the new element tag identified by the component's `selector` property. This displays the hero form when the application component is loaded. Don't forget to remove the `name` field from the class body as well. 这里只做了两处修改。 `template` 中只剩下这个新的元素标签,即组件的 `selector` 属性。这样当应用组件被加载时,就会显示这个英雄表单。 同样别忘了从类中移除了 `name` 字段。
## Create an initial HTML form template ## 创建初始 HTML 表单模板 Update the template file with the following contents: 修改模板文件,内容如下: The language is simply HTML5. You're presenting two of the `Hero` fields, `name` and `alterEgo`, and opening them up for user input in input boxes. 这只是一段普通的旧式 HTML 5 代码。这里有两个 `Hero` 字段,`name` 和 `alterEgo`,供用户输入。 The *Name* `` control has the HTML5 `required` attribute; the *Alter Ego* `` control does not because `alterEgo` is optional. *Name* `` 控件具有 HTML5 的 `required` 属性;但 *Alter Ego* `` 控件没有,因为 `alterEgo` 字段是可选的。 You added a *Submit* button at the bottom with some classes on it for styling. 在底部添加个 *Submit* 按钮,它还带一些 CSS 样式类。 *You're not using Angular yet*. There are no bindings or extra directives, just layout. **你还没有真正用到 Angular**。没有绑定,没有额外的指令,只有布局。
In template driven forms, if you've imported `FormsModule`, you don't have to do anything to the `
` tag in order to make use of `FormsModule`. Continue on to see how this works. 在模板驱动表单中,你只要导入了 `FormsModule` 就不用对 `` 做任何改动来使用 `FormsModule`。接下来你会看到它的原理。
The `container`, `form-group`, `form-control`, and `btn` classes come from [Twitter Bootstrap](http://getbootstrap.com/css/). These classes are purely cosmetic. Bootstrap gives the form a little style. `container`、`form-group`、`form-control` 和 `btn` 类来自 [Twitter Bootstrap](http://getbootstrap.com/css/)。这些类纯粹是装饰品。 Bootstrap 为这个表单提供了一些样式。
Angular forms don't require a style library
Angular 表单不需要任何样式库
Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or the styles of any external library. Angular apps can use any CSS library or none at all. Angular 不需要 `container`、`form-group`、`form-control` 和 `btn` 类, 或者外部库的任何样式。Angular 应用可以使用任何 CSS 库…… ,或者啥都不用。
To add the stylesheet, open `styles.css` and add the following import line at the top: 要添加样式表,就打开 `index.html`,并把下列链接添加到 `` 中: ## Add powers with _*ngFor_ ## 用 ***ngFor*** 添加超能力 The hero must choose one superpower from a fixed list of agency-approved powers. You maintain that list internally (in `HeroFormComponent`). 英雄必须从认证过的固定列表中选择一项超能力。 这个列表位于 `HeroFormComponent` 中。 You'll add a `select` to the form and bind the options to the `powers` list using `ngFor`, a technique seen previously in the [Displaying Data](guide/displaying-data) page. 在表单中添加 `select`,用 `ngFor` 把 `powers` 列表绑定到列表选项。 之前的[显示数据](guide/displaying-data)一章中见过 `ngFor`。 Add the following HTML *immediately below* the *Alter Ego* group: 在 *Alter Ego* 的紧下方添加如下 HTML: This code repeats the `