教程第三章译完

This commit is contained in:
Zhicheng Wang
2016-04-16 11:54:04 +08:00
3 changed files with 287 additions and 27 deletions
@@ -5,42 +5,58 @@ include ../_util-fns
especially if we'll need a great number of them, they're similar to each other, and they change frequently
to meet rapidly changing business and regulatory requirements.
我们不可能一直觉得手动编写表单和需要的工作量和时间成正比,特别是当我们需要编写大量的表单,他们非常类似,而且他们需要随着商务和政策需求的迅速变化而变化。
It may be more economical to create the forms dynamically, based on metadata that describe the business object model.
基于商务对象模型里面的元数据,动态建立表单可能更加划算。
In this cookbook we show how to use `ngFormModel` to dynamically render a simple form with different control types and validation.
It's a primitive start.
It might evolve to support a much richer variety of questions, more graceful rendering, and superior user experience.
All such greatness has humble beginnings.
在本文中,我们会展示怎么利用`ngFormModel`动态渲染一个简单的表单, 包含不同类型控制器和验证规则。
这是一个原始的开始,任何伟大都是从谦卑开始的。我们可以在这个基础上添加种类丰富的问卷问题,更加优美的渲染和更优越的用户体验。
In our example we use a dynamic form to build an online application experience for heroes seeking employment.
The agency is constantly tinkering with the application process.
We can create the forms on the fly *without changing our application code*.
We can create the forms on the fly *without changing our application code*.
在这个例子中,我们使用动态表单,为正在找工作的英雄们创建一个在线申请体验。中介在不断的修改申请流程。我们可以在*不修改程序*的情况下,动态即时的建立一个表格
<a id="toc"></a>
:marked
## Table of contents
[Question Model](#object-model)
[Form Component](#form-component)
[Questionnaire Metadata](#questionnaire-metadata)
## 目录
[问卷问题模型Question Model](#object-model)
[Dynamic Template](#dynamic-template)
[表单组件Form Component](#form-component)
[问卷元数据Questionnaire Metadata](#questionnaire-metadata)
[动态模板Dynamic Template](#dynamic-template)
:marked
**See the [live example](/resources/live-examples/cb-dynamic-form/ts/plnkr.html)**.
**请看[在线例子](/resources/live-examples/cb-dynamic-form/ts/plnkr.html)**.
.l-main-section
<a id="object-model"></a>
:marked
## Question Model
## 问卷问题模型
The first step is to define an object model that can describe all scenarios needed by the form functionality.
The hero application process involves a form with a lot of questions.
The "question" is the most fundamental object in the model.
第一步是定义一个对象模型,用来描述所有表单功能需要的场景。英雄申请流程涉及到一个有很多问卷问题的表单。问卷问题是最基础的对象模型。
We have created `QuestionBase` as the most fundamental question class.
下面是我们建立的非常基础的问卷问题类,名叫`QuestionBase`。
+makeExample('cb-dynamic-form/ts/app/question-base.ts','','app/question-base.ts')
@@ -48,28 +64,40 @@ include ../_util-fns
From this base we derived two new classes in `TextboxQuestion` and `DropdownQuestion` that represent Textbox and Dropdown questions.
The idea is that the form will be bound to specific question types and render the appropriate controls dynamically.
在这个基础上,我们衍生了两个新类`TextboxQuestion` 和 `DropdownQuestion`,分别代表文本框和下拉框。这么做的初衷是,表单能动态的绑定特定的问卷问题类型,并动态渲染合适的控制器。
`TextboxQuestion` supports multiple html5 types like text, email, url etc via the `type` property.
`TextboxQuestion`通过`type`属性,支持多种HTML5元素类型,比如文本、邮件、网址等。
+makeExample('cb-dynamic-form/ts/app/question-textbox.ts',null,'app/question-textbox.ts')(format='.')
:marked
`DropdownQuestion` presents a list of choices in a select box.
`DropdownQuestion`代表一个拥有一个列表可选项目的选择框。
+makeExample('cb-dynamic-form/ts/app/question-dropdown.ts',null,'app/question-dropdown.ts')(format='.')
:marked
Next we have defined `QuestionControlService`, a simple service for transforming our questions to an ngForm control group.
In a nutshell, the control group consumes the metadata from the question model and allows us to specify default values and validation rules.
下一步,我们定义了`QuestionControlService`,一个可以把我们的问卷问题转换为ngForm控制组的服务。简而言之,这个ngForm控制组使用问卷模型的元数据,允许我们制定默认值和验证规则。
+makeExample('cb-dynamic-form/ts/app/question-control.service.ts',null,'app/question-control.service.ts')(format='.')
<a id="form-component"></a>
:marked
## Question form components
## 问卷表单组件
Now that we have defined the complete model we are ready to create components to represent the dynamic form.
现在我们已经有一个已定义的完整模型,我们可以创建一个动态表单的组件。
:marked
`DynamicForm` is the entry point and the main container for the form.
`DynamicForm` is the entry point and the main container for the form.
`DynamicForm`是我们表单的主要载体和切入口。
+makeTabs(
`cb-dynamic-form/ts/app/dynamic-form.component.html,
cb-dynamic-form/ts/app/dynamic-form.component.ts`,
@@ -81,7 +109,9 @@ include ../_util-fns
It presents a list of questions, each question bound to a `<df-question>` component element.
The `<df-question>` tag matches the `DynamicFormQuestionComponent`,
the component responsible for rendering the details of each _individual_ question based on values in the data-bound question object.
它显示一个问卷问题的列表,每个问题都在`<df-question>`组件元素之内。`<df-question>`对应于`DynamicFormQuestionComponent`,该组件的作用是根据问卷问题对象的值来渲染每个问卷问题的细节。
+makeTabs(
`cb-dynamic-form/ts/app/dynamic-form-question.component.html,
cb-dynamic-form/ts/app/dynamic-form-question.component.ts`,
@@ -94,50 +124,74 @@ include ../_util-fns
We only have two types of questions at this point but we can imagine many more.
The `ngSwitch` determines which type of question to display.
请注意,这个组件能代表模型里的任何问卷问题类型。目前,我们只有两种类型的问卷问题,但是我们可以添加更多类型。`ngSwitch`确定显示哪一个类型的问卷问题。
In both components we're relying on Angular's **ngFormModel** to connect the template HTML to the
underlying control objects, populated from the question model with display and validation rules.
在两个组件中,我们依赖Angular的**ngFormModel**来把模板HTML链接到底层控制对象,该对象已经从问卷问题模型里获取了显示和验证规则,
<a id="questionnaire-metadata"></a>
:marked
## Questionnaire data
## 问卷数据
:marked
`DynamicForm` expects the list of questions in the form of an array bound to `@Input() questions`.
`DynamicForm`预期得到一个问题列表,该列表是一个关联到`@Input() questions`排列。
The set of questions we have defined for the job application is returned from the `QuestionService`.
In a real app we'd retrieve these questions from storage.
`QuestionService`返回我们在定义工作申请表的时候设定的这套问题。在一个真实的应用程序中,我们会从存储库里面提取。
The key point is that we control the hero job application questions entirely through the objects returned from `QuestionService`.
Questionnaire maintenance is a simple matter of adding, updating, and removing objects from the `questions` array.
最关键的点是,我们全部通过从`QuestionService`返回的对象,来控制英雄工作申请问卷。要维护问卷,我们要做的是非常简单的添加、更新和删除`问题`排列中的对象。
+makeExample('cb-dynamic-form/ts/app/question.service.ts','','app/question.service.ts')
:marked
Finally, we display an instance of the form in the `AppComponent` shell.
最后,我们在`AppComponent`里面显示表单。
+makeExample('cb-dynamic-form/ts/app/app.component.ts','','app.component.ts')
<a id="dynamic-template"></a>
:marked
## Dynamic Template
## 动态模板
Although in this example we're modelling a job application for heroes, there are no references to any specific hero question
outside the objects returned by `QuestionService`.
虽然在这个例子中,我们是在为英雄工作申请表建模,但是除了`QuestionService`返回的对象外,没有其他任何地方有指定英雄问卷相关的内容。
This is very important since it allows us to repurpose the components for any type of survey
as long as it's compatible with our *question* object model.
The key is the dynamic data binding of metadata used to render the form
without making any hardcoded assumptions about specific questions.
In addition to control metadata, we are also adding validation dynamically.
这点非常重要的,因为只要与我们的*问卷*对象模型兼容,它允许我们为任何类型的调查重复使用这些组件。关键是运用动态数据绑定的元数据来渲染表单,不对问卷问题有任何硬性的假设。除了控制起元数据外,我们还可以动态添加验证规则。
The *Save* button is disabled until the form is in a valid state.
When the form is valid, we can click *Save* and the app renders the current form values as JSON.
This proves that any user input is bound back to the data model.
Saving and retrieving the data is an exercise for another time.
在表单的验证通过之前,*保存*按钮是禁用的。当表单验证通过后,我们可以点击*保存*,程序会把当前的值渲染成为Json。它证明了任何用户输入都被传到了数据模型。如何储存和提取数据是另一个问题。
:marked
The final form looks like this:
完整表单看起来是这样:
figure.image-display
img(src="/resources/images/cookbooks/dynamic-form/dynamic-form.png" alt="Dynamic-Form")
:marked
[Back to top](#top)
[Back to top](#top)
[回到顶部](#top)
File diff suppressed because it is too large Load Diff
+81 -3
View File
@@ -179,30 +179,47 @@ code-example(format="." language="bash").
:marked
#### The *hero* property is an ***input***
#### *hero* 属性是一个 ***输入***
#### *hero* 是一个 ***输入*** 属性
The `HeroDetailComponent` must be told what hero to display. Who will tell it? The parent `AppComponent`!
还得告诉`HeroDetailComponent`显示哪个英雄。谁告诉它呢?自然是父组件`AppComponent`!
The `AppComponent` knows which hero to show: the hero that the user selected from the list.
The user's selection is in its `selectedHero` property.
`AppComponent`自然知道该显示哪个英雄:用户从列表中选中的那个。
这个英雄就是`selectedHero`属性的值。
We will soon update the `AppComponent` template so that it binds its `selectedHero` property
to the `hero` property of our `HeroDetailComponent`. The binding *might* look like this:
我们马上就要升级`AppComponent`模板,以便把该组件的`selectedHero`属性绑定到`HeroDetailComponent`组件的`hero`属性上。
绑定看起来可能是这样的:
code-example(format=".").
&lt;my-hero-detail [hero]="selectedHero">&lt;/my-hero-detail>
:marked
Notice that the `hero` property is the ***target*** of a property binding &mdash; it's in square brackets to the left of the (=).
注意,在等号(=)左边方括号中的这个`hero`是属性绑定的目标。
Angular insists that we declare a ***target*** property to be an ***input*** property.
If we don't, Angular rejects the binding and throws an error.
Angular期望我们把 ***目标属性*** 定义成组件的 ***输入属性*** ,否则,Angular会拒绝绑定,并且抛出一个错误。
.l-sub-section
:marked
We explain input properties in more detail [here](../guide/attribute-directives.html#why-input)
where we also explain why *target* properties require this special treament and
where we also explain why *target* properties require this special treatment and
*source* properties do not.
我们在[这里](../guide/attribute-directives.html#why-input)详细解释了输入属性,以及为什么 *目标属性* 需要这种特殊待遇,而 *来源属性* 却不需要。
:marked
There are a couple of ways we can declare that `hero` is an *input*.
We'll do it the way we *prefer*, by annotating the `hero` property with the `@Input` decorator that we imported earlier.
我们有一大堆方式把`hero`声明成 *输入属性* 。
这里我们采用建议的方式:使用我们前面导入的`@Input`装饰器,为`hero`属性加上注解。
+makeExample('toh-3/ts/app/hero-detail.component.ts', 'hero-input')(format='.')
.l-sub-section
@@ -210,65 +227,106 @@ code-example(format=".").
Learn more about the `@Input()` decorator in the
[Attribute Directives](../guide/attribute-directives.html#input) chapter.
要了解`@Input()`装饰器的更多知识,参见[属性型指令](../guide/attribute-directives.html#input)一章。
.l-main-section
:marked
## Refresh the AppComponent
## 刷新AppComponent
We return to the `AppComponent` and teach it to use the `HeroDetailComponent`.
回到`AppComponent`组件,我们要教它使用`HeroDetailComponent`组件。
We begin by importing the `HeroDetailComponent` so we can refer to it.
我们先导入`HeroDetailComponent`组件,好让我们可以引用它。
+makeExample('toh-3/ts/app/app.component.ts', 'hero-detail-import')
:marked
Find the location in the template where we removed the *Hero Detail* content
and add an element tag that represents the `HeroDetailComponent`.
找到我们刚刚从模板中移除 *英雄详情* 的地方,放上用来表示`HeroDetailComponent`组件的HTML标记。
code-example(format=".").
&lt;my-hero-detail>&lt;/my-hero-detail>
.l-sub-section
:marked
*my-hero-detail* is the name we set as the `selector` in the `HeroDetailComponent` metadata.
*my-hero-detail*是我们在`HeroDetailComponent`元数据中的`selector`属性所指定的名字。
:marked
The two components won't coordinate until we bind the `selectedHero` property of the `AppComponent`
to the `HeroDetailComponent` element's `hero` property like this:
这两个组件目前还不能协同工作,直到我们把`AppComponent`组件的`selectedHero`属性和`HeroDetailComponent`组件的`hero`属性绑定在一起,就像这样:
code-example(format=".")
&lt;my-hero-detail [hero]="selectedHero">&lt;/my-hero-detail>
:marked
The `AppComponent`’s template should now look like this
`AppComponent`的模板是这样的:
+makeExample('toh-3/ts/app/app.component.ts', 'hero-detail-template', 'app.component.ts (Template)')
+makeExample('toh-3/ts/app/app.component.ts', 'hero-detail-template', 'app.component.ts (模板)')
:marked
Thanks to the binding, the `HeroDetailComponent` should receive the hero from the `AppComponent` and display that hero's detail beneath the list.
The detail should update every time the user picks a new hero.
感谢数据绑定机制,`HeroDetailComponent`组件可以从`AppComponent`组件中获取英雄数据,并且在列表的下方显示英雄的详情。
每当用户选中一个新的英雄时,详情信息应该随之更新。
It's not happening yet!
但什么也没有发生!
We click among the heroes. No details. We look for an error in the console of the browser development tools. No error.
我们点击了一个英雄,但没有显示详情。我们打开浏览器开发工具的控制台窗口,也看不到任何错误。
It is as if Angular were ignoring the new tag. That's because *it is ignoring the new tag*.
看起来像是Angular忽略了这个新标记。确实,这是因为 *它忽略了不认识的标记* 。
### The *directives* array
### *directives* 数组
A browser ignores HTML tags and attributes that it doesn't recognize. So does Angular.
浏览器会忽略它不认识的HTML标记和属性。Angular也是这样。
We've imported `HeroDetailComponent`, we've used it in the template, but we haven't told Angular about it.
我们引入了`HeroDetailComponent`,并在模板中使用它,但Angular还不知道它。
We tell Angular about it by listing it in the metadata `directives` array. Let's add that array property to the bottom of the
`@Component` configuration object, immediately after the `template` and `styles` properties.
我们把它列在元数据的`directies`数组中,这样Angular才会知道它。
我们把这个数组属性加在`@Component`配置对象的底部,`template`和`styles`属性中间。
+makeExample('toh-3/ts/app/app.component.ts', 'directives')
:marked
### It works!
### 搞定!
When we view our app in the browser we see the list of heroes.
When we select a hero we can see the selected hero’s details.
当在浏览器中查看应用时,我们看到了英雄列表。
当选中一个英雄时,我们还能看到所选英雄的详情。
What's fundamentally new is that we can use this `HeroDetailComponent`
to show hero details anywhere in the app.
我们所关注的进步是:我们可以在应用中的任何地方使用这个`HeroDetailComponent`组件来显示英雄详情了。
We’ve created our first reusable component!
我们创建了第一个可复用组件!
### Reviewing the App Structure
### 回顾应用结构
Let’s verify that we have the following structure after all of our good refactoring in this chapter:
来验证下吧,在本章中,经过这些漂亮的重构,我们应该得到下列结构:
.filetree
.file angular2-tour-of-heroes
.children
@@ -286,6 +344,8 @@ code-example(format=".")
.file typings.json
:marked
Here are the code files we discussed in this chapter.
这就是我们在本章讨论过的这些源码文件:
+makeTabs(`
toh-3/ts/app/hero-detail.component.ts,
@@ -300,23 +360,41 @@ code-example(format=".")
.l-main-section
:marked
## The Road We’ve Travelled
## 走过的路
Let’s take stock of what we’ve built.
来盘点一下我们已经构建完的部分。
* We created a reusable component
* 我们创建了一个可复用组件
* We learned how to make a component accept input
* 我们学会了如何让一个组件接收输入
* We learned to bind a parent component to a child component.
* 我们学会了把父组件绑定到子组件。
* We learned to declare the application directives we need in a `directives` array.
* 我们学会了在`directives`中定义应用所需的指令。
[Run the live example for part 3](/resources/live-examples/toh-3/ts/plnkr.html).
[运行第三部分的鲜活范例](/resources/live-examples/toh-3/ts/plnkr.html).
.l-main-section
:marked
## The Road Ahead
## 前方的路
Our Tour of Heroes has become more reusable with shared components.
通过抽取共享组件,我们的《英雄之旅》变得更加可复用了。
We're still getting our (mock) data within the `AppComponent`.
That's not sustainable.
We should refactor data access to a separate service
and share it among the components that need data.
在`AppComponent`中,我们仍然使用着mock数据。
这种方式可没法“可持续发展”。
我们要把数据访问逻辑抽取到一个独立的服务中,并在需要数据的组件之间共享。
We’ll learn to create services in the [next tutorial](toh-pt4.html) chapter.
在[下一步](toh-pt4.html)中,我们将学习如何创建服务。