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:
用组件和模板构建 Angular 表单
Build an Angular form with a component and template.
用ngModel创建双向数据绑定,以读取和写入输入控件的值
Use ngModel to create two-way data bindings for reading and writing input-control values.
跟踪状态的变化,并验证表单控件
Track state changes and the validity of form controls.
使用特殊的CSS类来跟踪控件的状态并给出视觉反馈
Provide visual feedback using special CSS classes that track the state of the controls.
向用户显示验证错误提示,以及启用/禁用表单控件
Display validation errors to users and enable/disable form controls.
使用模板引用变量在 HTML 元素之间共享信息
Share information across HTML elements using template reference variables.
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.
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:
注意,提交按钮被禁用了,而且输入控件左侧的“必填”条从绿色变为了红色。
Note that the Submit button is disabled, and the "required" bar to the left of the input control changes from green to red.
稍后,会使用标准 CSS 来定制“必填”条的颜色和位置。
You can customize the colors and location of the "required" bar with standard CSS.
我们将一点点构建出此表单:
You'll build this form in small steps:
创建Hero模型类
Create the Hero model class.
创建控制此表单的组件。
Create the component that controls the form.
创建具有初始表单布局的模板。
Create a template with the initial form layout.
使用ngModel双向数据绑定语法把数据属性绑定到每个表单输入控件。
Bind data properties to each form control using the ngModel two-way data-binding syntax.
往每个表单输入控件上添加name属性 (attribute)。
Add a name attribute to each form-input control.
添加自定义 CSS 来提供视觉反馈。
Add custom CSS to provide visual feedback.
显示和隐藏有效性验证的错误信息。
Show and hide validation-error messages.
使用 ngSubmit 处理表单提交。
Handle form submission with ngSubmit.
禁用此表单的提交按钮,直到表单变为有效。
Disable the form’s Submit button until the form is valid.
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.
最简单的模型是个“属性包”,用来存放应用中一件事物的事实。
这里使用三个必备字段 (id、name、power),和一个可选字段 (alterEgo,译注:中文含义是第二人格,例如 X 战警中的 Jean / 黑凤凰)。
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).
在应用文件夹中创建下列文件:
In the app directory, create the following file with the given content:
src/app/hero.ts
exportclassHero{
constructor(
public id: number,
public name:string,
public power:string,
public alterEgo?:string
){}
}
这是一个少量需求和零行为的贫血模型。对演示来说很完美。
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.
alterEgo是可选的,调用构造函数时可省略,注意alterEgo?中的问号 (?)。
The alterEgo is optional, so the constructor lets you omit it; note the question mark (?) in alterEgo?.
可以这样创建新英雄:
You can create a new hero like this:
let myHero =newHero(42,'SkyDog','Fetch any object at any distance','Leslie Rollover');
console.log('My hero is called '+ myHero.name);// "My hero is called SkyDog"
创建表单组件
Create a form component
Angular 表单分为两部分:基于 HTML 的模板和组件类,用来程序处理数据和用户交互。
先从组件类开始,是因为它可以简要说明英雄编辑器能做什么。
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.
创建下列文件:
Create the following file with the given content:
src/app/hero-form.component.ts (v1)
import{Component}from'@angular/core';import{Hero}from'./hero';@Component({
selector:'hero-form',
templateUrl:'./hero-form.component.html'})exportclassHeroFormComponent{
powers =['Really Smart','Super Flexible','Super Hot','Weather Changer'];
model =newHero(18,'Dr IQ',this.powers[0],'Chuck Overstreet');
submitted =false;
onSubmit(){this.submitted =true;}// TODO: Remove this when we're doneget diagnostic(){return JSON.stringify(this.model);}}
这个组件没有什么特别的地方,没有表单相关的东西,与之前写过的组件没什么不同。
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.
这段代码导入了Angular核心库以及我们刚刚创建的Hero模型。
The code imports the Angular core library and the Hero model you just created.
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 on the
Template Syntax page) for binding to a
parent component. This is not a concern now and these future changes won't affect the form.
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.
为何分离模板文件?
Why the separate template file?
为什么不与我们在其他地方常常做的那样,以内联的方式把模板写在组件文件中呢?
Why don't you write the template inline in the component file as you often do elsewhere?
没有什么答案在所有场合都总是“正确”的。当模板足够短的时候,内联形式更招人喜欢。
但大多数的表单模板都不短。通常,TypeScript 和 JavaScript 文件不是写(读)大型 HTML 的好地方,
而且没有几个编辑器能对混写的 HTML 和代码提供足够的帮助。
我们还是喜欢内容清晰、目标明确的短文件,像这个一样。
There is no "right" answer for all occasions. Inline templates are useful when they are short.
Most form templates aren't short. TypeScript and JavaScript files generally aren't the best place to
write (or read) large stretches of HTML, and few editors help with files that have a mix of HTML and code.
就算是在仅仅显示少数表单项目时,表单模板一般都比较庞大。所以通常最好的方式是将 HTML 模板放到单独的文件中。
一会儿将编写这个模板文件。在这之前,先退一步,再看看app.module.ts和app.component.ts,让它们使用新的HeroFormComponent。
Form templates tend to be large, even when displaying a small number of fields,
so it's usually best to put the HTML template in a separate file.
You'll write that template file in a moment. First,
revise the app.module.ts and app.component.ts to make use of the new HeroFormComponent.
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.
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.
把“快速起步”版的文件替换为如下内容:
Replace the contents of the "QuickStart" version with the following:
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.
You add the HeroFormComponent to the list of declarations defined in the ngModule decorator. This makes
the HeroFormComponent component visible throughout this module.
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.
修改 app.component.ts
Revise app.component.ts
AppComponent是应用的根组件,HeroFormComponent将被放在其中。
AppComponent is the application's root component. It will host the new HeroFormComponent.
把“快速起步”的版本内容替换成下列代码:
Replace the contents of the "QuickStart" version 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.
You've also dropped the name field from the class body.
创建初始 HTML 表单模板
Create an initial HTML form template
用下列内容新建模板文件:
Create the template file with the following contents:
In template driven forms, if you've imported FormsModule, you don't have to do anything
to the <form> tag in order to make use of FormsModule. Continue on to see how this works.
The container, form-group, form-control, and btn classes
come from Twitter Bootstrap. These classes are purely cosmetic.
Bootstrap gives the form a little style.
Angular 表单不需要任何样式库Angular forms don't require a style library
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.
我们来添加样式表。打开index.html,并把下列链接添加到<head>中:
To add the stylesheet, open index.html and add the following link to the <head>:
This code repeats the <option> tag for each power in the list of powers.
The pow template input variable is a different power in each iteration;
you display its name using the interpolation syntax.
使用 ngModel 进行双向数据绑定
Two-way data binding with ngModel
如果立即运行此应用,你将会失望。
Running the app right now would be disappointing.
因为还没有绑定到某个英雄,所以看不到任何数据。
解决方案见前面的章节。
显示数据介绍了属性绑定。
用户输入介绍了如何通过事件绑定来监听 DOM 事件,以及如何用显示值更新组件的属性。
You don't see hero data because you're not binding to the Hero yet.
You know how to do that from earlier pages.
Displaying Data teaches property binding.
User Input shows how to listen for DOM events with an
event binding and how to update a component property with the displayed value.
现在,需要同时进行显示、监听和提取。
Now you need to display, listen, and extract at the same time.
The NgForm directive supplements the form element with additional features.
It holds the controls you created for the elements with an ngModel directive
and name attribute, and monitors their properties, including their validity.
It also has its own valid property which is true only if every contained
control is valid.
If you ran the app now and started typing in the Name input box,
adding and deleting characters, you'd see them appear and disappear
from the interpolated text.
At some point it might look like this:
诊断信息可以证明,数据确实从输入框流动到模型,再反向流动回来。
The diagnostic is evidence that values really are flowing from the input box to the model and
back again.
Notice that you also added a name attribute to the <input> tag and set it to "name",
which makes sense for the hero's name. Any unique value will do, but using a descriptive name is helpful.
Defining a name attribute is a requirement when using [(ngModel)] in combination with a form.
Internally, Angular creates FormControl instances and
registers them with an NgForm directive that Angular attached to the <form> tag.
Each FormControl is registered under the name you assigned to the name attribute.
Read more in the previous section, The NgForm directive.
Add similar [(ngModel)] bindings and name attributes to Alter Ego and Hero Power.
You'll ditch the input box binding message
and add a new binding (at the top) to the component's diagnostic property.
Then you can confirm that two-way data binding works for the entire hero model.
修改之后,这个表单的核心是这样的:
After revision, the core of the form should look like this:
Using ngModel in a form gives you more than just two-way data binding. It also tells
you if the user touched the control, if the value changed, or if the value became invalid.
The NgModel directive doesn't just track state; it updates the control with special Angular CSS classes that reflect the state.
You can leverage those class names to change the appearance of the control.
The ng-valid/ng-invalid pair is the most interesting, because you want to send a
strong visual signal when the values are invalid. You also want to mark required fields.
To create such visual feedback, add definitions for the ng-* CSS classes.
删除模板引用变量#spy和TODO,因为它们已经完成了使命。
Delete the #spy template reference variable and the TODO as they have served their purpose.
添加用于视觉反馈的自定义 CSS
Add custom CSS for visual feedback
可以在输入框的左侧添加带颜色的竖条,用于标记必填字段和无效输入:
You can mark required fields and invalid data at the same time with a colored bar
on the left of the input box:
You can improve the form. The Name input box is required and clearing it turns the bar red.
That says something is wrong but the user doesn't know what is wrong or what to do about it.
Leverage the control's state to reveal a helpful message.
当用户删除姓名时,应该是这样的:
When the user deletes the name, the form should look like this:
要达到这个效果,在<input>标签中添加:
To achieve this effect, extend the <input> tag with the following:
You need a template reference variable to access the input box's Angular control from within the template.
Here you created a variable called name and gave it the value "ngModel".
Why "ngModel"?
A directive's exportAs property
tells Angular how to link the reference variable to the directive.
You set name to ngModel because the ngModel directive's exportAs property happens to be "ngModel".
In this example, you hide the message when the control is valid or pristine;
"pristine" means the user hasn't changed the value since it was displayed in this form.
This user experience is the developer's choice. Some developers want the message to display at all times.
If you ignore the pristine state, you would hide the message only when the value is valid.
If you arrive in this component with a new (blank) hero or an invalid hero,
you'll see the error message immediately, before you've done anything.
Some developers want the message to display only when the user makes an invalid change.
Hiding the message while the control is "pristine" achieves that goal.
You'll see the significance of this choice when you add a new hero to the form.
英雄的第二人格是可选项,所以不用改它。
The hero Alter Ego is optional so you can leave that be.
Hero Power selection is required.
You can add the same kind of error handling to the <select> if you want,
but it's not imperative because the selection box already constrains the
power to valid values.
再次运行应用,点击 New Hero 按钮,表单被清空了。
输入框左侧的必填项竖条是红色的,表示name和power属性是无效的。
这可以理解,因为有一些必填字段。
错误信息是隐藏的,因为表单还是全新的,还没有修改任何东西。
Run the application again, click the New Hero button, and the form clears.
The required bars to the left of the input box are red, indicating invalid name and power properties.
That's understandable as these are required fields.
The error messages are hidden because the form is pristine; you haven't changed anything yet.
输入名字,再次点击 New Hero 按钮。
这次,出现了错误信息!为什么?我们不希望显示新(空)的英雄时,出现错误信息。
Enter a name and click New Hero again.
The app displays a Name is required error message.
You don't want error messages when you create a new (empty) hero.
Why are you getting one now?
使用浏览器工具审查这个元素就会发现,这个 name 输入框并不是全新的。
表单记得我们在点击 New Hero 前输入的名字。
更换了英雄并不会重置控件的“全新”状态。
Inspecting the element in the browser tools reveals that the name input box is no longer pristine.
The form remembers that you entered a name before clicking New Hero.
Replacing the hero did not restore the pristine state of the form controls.
我们必须清除所有标记,在调用newHero()方法后调用表单的reset()方法即可。
You have to clear all of the flags imperatively, which you can do
by calling the form's reset() method after calling the newHero() method.
Now clicking "New Hero" resets both the form and its control flags.
使用 ngSubmit 提交该表单
Submit the form with ngSubmit
在填表完成之后,用户还应该能提交这个表单。
“Submit(提交)”按钮位于表单的底部,它自己不做任何事,但因为有特殊的 type 值 (type="submit"),所以会触发表单提交。
The user should be able to submit this form after filling it in.
The Submit button at the bottom of the form
does nothing on its own, but it will
trigger a form submit because of its type (type="submit").
You'd already defined a template reference variable,
#heroForm, and initialized it with the value "ngForm".
Now, use that variable to access the form with the Submit button.
我们要把表单的总体有效性通过heroForm变量绑定到此按钮的disabled属性上,代码如下:
You'll bind the form's overall validity via
the heroForm variable to the button's disabled property
using an event binding. Here's the code:
Not impressed? Think about it for a moment. What would you have to do to
wire the button's enable/disabled state to the form's validity without Angular's help?
有了 Angular,它就是这么简单:
For you, it was as simple as this:
定义模板引用变量,放在(强化过的)form 元素上
Define a template reference variable on the (enhanced) form element.
从很多行之外的按钮上引用这个变量。
Refer to that variable in a button many lines away.
切换两个表单区域(额外的奖励)
Toggle two form regions (extra credit)
提交表单还是不够激动人心。
Submitting the form isn't terribly dramatic at the moment.
An unsurprising observation for a demo. To be honest,
jazzing it up won't teach you anything new about forms.
But this is an opportunity to exercise some of your newly won
binding skills.
If you aren't interested, skip to this page's conclusion.
来实现一些更炫的视觉效果吧。
隐藏掉数据输入框,显示一些其它东西。
For a more strikingly visual effect,
hide the data entry area and display something else.
The main form is visible from the start because the
submitted property is false until you submit the form,
as this fragment from the HeroFormComponent shows:
The HTML includes an Edit button whose click event is bound to an expression
that clears the submitted flag.
当点Edit按钮时,这个只读块消失了,可编辑的表单重新出现了。
When you click the Edit button, this block disappears and the editable form reappears.
结论
Conclusion
本章讨论的 Angular 表单技术利用了下列框架特性来支持数据修改、验证和更多操作:
The Angular form discussed in this page takes advantage of the following
framework features to provide support for data modification, validation, and more:
Angular HTML 表单模板。
An Angular HTML form template.
带有@Component装饰器的表单组件类。
A form component class with a @Component decorator.
通过绑定到NgForm.ngSubmit事件属性来处理表单提交。
Handling form submission by binding to the NgForm.ngSubmit event property.
模板引用变量,例如#heroForm和#name。
Template-reference variables such as #heroForm and #name.
[(ngModel)]语法用来实现双向数据绑定。
[(ngModel)] syntax for two-way data binding.
name属性的用途是有效性验证和对表单元素的变更进行追踪。
The use of name attributes for validation and form-element change tracking.