教程第三章译完
This commit is contained in:
@@ -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
@@ -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=".").
|
||||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||
:marked
|
||||
Notice that the `hero` property is the ***target*** of a property binding — 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=".").
|
||||
<my-hero-detail></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=".")
|
||||
<my-hero-detail [hero]="selectedHero"></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)中,我们将学习如何创建服务。
|
||||
|
||||
Reference in New Issue
Block a user