# Conflicts: # aio/content/cli/index.md # aio/content/guide/accessibility.md # aio/content/guide/ajs-quick-reference.md # aio/content/guide/angular-compiler-options.md # aio/content/guide/animations.md # aio/content/guide/aot-compiler.md # aio/content/guide/architecture-components.md # aio/content/guide/architecture-modules.md # aio/content/guide/architecture-next-steps.md # aio/content/guide/architecture-services.md # aio/content/guide/attribute-directives.md # aio/content/guide/bootstrapping.md # aio/content/guide/browser-support.md # aio/content/guide/cli-builder.md # aio/content/guide/comparing-observables.md # aio/content/guide/component-styles.md # aio/content/guide/creating-libraries.md # aio/content/guide/dependency-injection-in-action.md # aio/content/guide/dependency-injection-navtree.md # aio/content/guide/dependency-injection-providers.md # aio/content/guide/dependency-injection.md # aio/content/guide/deployment.md # aio/content/guide/deprecations.md # aio/content/guide/displaying-data.md # aio/content/guide/dynamic-component-loader.md # aio/content/guide/elements.md # aio/content/guide/file-structure.md # aio/content/guide/glossary.md # aio/content/guide/http.md # aio/content/guide/i18n.md # aio/content/guide/ivy-compatibility.md # aio/content/guide/language-service.md # aio/content/guide/lifecycle-hooks.md # aio/content/guide/module-types.md # aio/content/guide/ngmodule-faq.md # aio/content/guide/ngmodule-vs-jsmodule.md # aio/content/guide/ngmodules.md # aio/content/guide/observables-in-angular.md # aio/content/guide/pipes.md # aio/content/guide/providers.md # aio/content/guide/releases.md # aio/content/guide/route-animations.md # aio/content/guide/router-tutorial.md # aio/content/guide/router.md # aio/content/guide/rx-library.md # aio/content/guide/schematics-authoring.md # aio/content/guide/schematics-for-libraries.md # aio/content/guide/service-worker-config.md # aio/content/guide/service-worker-getting-started.md # aio/content/guide/structural-directives.md # aio/content/guide/styleguide.md # aio/content/guide/template-syntax.md # aio/content/guide/template-typecheck.md # aio/content/guide/testing.md # aio/content/guide/typescript-configuration.md # aio/content/guide/upgrade-setup.md # aio/content/guide/user-input.md # aio/content/guide/using-libraries.md # aio/content/guide/web-worker.md # aio/content/guide/workspace-config.md # aio/content/marketing/docs.md # aio/content/marketing/events.html # aio/content/navigation.json # aio/content/start/index.md # aio/content/start/start-data.md # aio/content/start/start-deployment.md # aio/content/tutorial/toh-pt2.md # aio/content/tutorial/toh-pt4.md # aio/package.json # aio/src/app/app.component.ts # aio/src/app/layout/footer/footer.component.html # aio/src/app/shared/custom-icon-registry.ts # aio/src/environments/environment.stable.ts # aio/tools/stackblitz-builder/builder.js # aio/tools/transforms/angular-content-package/index.js # aio/tools/transforms/templates/api/lib/memberHelpers.html # aio/yarn.lock # integration/ng_elements/e2e/app.e2e-spec.ts # integration/ng_elements/src/app.ts # packages/common/src/pipes/date_pipe.ts # packages/core/src/metadata/directives.ts # packages/core/src/metadata/ng_module.ts # packages/core/src/render/api.ts # packages/forms/src/directives/ng_model.ts # packages/forms/src/form_builder.ts # packages/forms/src/model.ts # packages/platform-browser/src/browser.ts # packages/router/src/config.ts # packages/router/src/directives/router_link.ts # packages/router/src/directives/router_link_active.ts # packages/router/src/events.ts # packages/router/src/router.ts # packages/router/src/router_module.ts # packages/router/src/router_state.ts
		
			
				
	
	
		
			324 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			324 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # Building dynamic forms
 | ||
| 
 | ||
| # 构建动态表单
 | ||
| 
 | ||
| Many forms, such as questionaires, can be very similar to one another in format and intent.
 | ||
| To make it faster and easier to generate different versions of such a form,
 | ||
| you can create a *dynamic form template* based on metadata that describes the business object model.
 | ||
| You can then use the template to generate new forms automatically, according to changes in the data model.
 | ||
| 
 | ||
| 许多表单(例如问卷)可能在格式和意图上都非常相似。为了更快更轻松地生成这种表单的不同版本,你可以根据描述业务对象模型的元数据来创建*动态表单模板*。然后就可以根据数据模型中的变化,使用该模板自动生成新的表单。
 | ||
| 
 | ||
| The technique is particularly useful when you have a type of form whose content must
 | ||
| change frequently to meet rapidly changing business and regulatory requirements.
 | ||
| A typical use case is a questionaire. You might need to get input from users in different contexts.
 | ||
| The format and style of the forms a user sees should remain constant, while the actual questions you need to ask vary with the context.
 | ||
| 
 | ||
| 如果你有这样一种表单,其内容必须经常更改以满足快速变化的业务需求和监管需求,该技术就特别有用。一个典型的例子就是问卷。你可能需要在不同的上下文中获取用户的意见。用户要看到的表单格式和样式应该保持不变,而你要提的实际问题则会因上下文而异。
 | ||
| 
 | ||
| In this tutorial you will build a dynamic form that presents a basic questionaire.
 | ||
| You will build an online application for heroes seeking employment.
 | ||
| The agency is constantly tinkering with the application process, but by using the dynamic form
 | ||
| you can create the new forms on the fly without changing the application code.
 | ||
| 
 | ||
| 在本教程中,你将构建一个渲染基本问卷的动态表单。你要为正在找工作的英雄们建立一个在线应用。英雄管理局会不断修补应用流程,但是借助动态表单,你可以动态创建新的表单,而无需修改应用代码。
 | ||
| 
 | ||
| The tutorial walks you through the following steps.
 | ||
| 
 | ||
| 本教程将指导你完成以下步骤。
 | ||
| 
 | ||
| 1. Enable reactive forms for a project.
 | ||
| 
 | ||
|    为项目启用响应式表单。
 | ||
| 
 | ||
| 2. Establish a data model to represent form controls.
 | ||
| 
 | ||
|    建立一个数据模型来表示表单控件。
 | ||
| 
 | ||
| 3. Populate the model with sample data.
 | ||
| 
 | ||
|    使用范例数据填充模型。
 | ||
| 
 | ||
| 4. Develop a component to create form controls dynamically.
 | ||
| 
 | ||
|    开发一个组件来动态创建表单控件。
 | ||
| 
 | ||
| The form you create uses input validation and styling to improve the user experience.
 | ||
| It has a Submit button that is only enabled when all user input is valid, and flags invalid input with color coding and error messages.
 | ||
| 
 | ||
| 你创建的表单会使用输入验证和样式来改善用户体验。它有一个 Submit 按钮,这个按钮只会在所有的用户输入都有效时启用,并用色彩和一些错误信息来标记出无效输入。
 | ||
| 
 | ||
| The basic version can evolve to support a richer variety of questions, more graceful rendering, and superior user experience.
 | ||
| 
 | ||
| 这个基本版可以不断演进,以支持更多的问题类型、更优雅的渲染体验以及更高大上的用户体验。
 | ||
| 
 | ||
| <div class="alert is-helpful">
 | ||
| 
 | ||
| See the <live-example name="dynamic-form"></live-example>.
 | ||
| 
 | ||
| 参阅 <live-example name="dynamic-form"></live-example>。
 | ||
| 
 | ||
| </div>
 | ||
| 
 | ||
| ## Prerequisites
 | ||
| 
 | ||
| ## 先决条件
 | ||
| 
 | ||
| Before doing this tutorial, you should have a basic understanding to the following.
 | ||
| 
 | ||
| 在做本教程之前,你应该对下列内容有一个基本的了解。
 | ||
| 
 | ||
| * [TypeScript](https://www.typescriptlang.org/docs/home.html "The TypeScript language") and HTML5 programming.
 | ||
| 
 | ||
|   [TypeScript](https://www.typescriptlang.org/docs/home.html "TypeScript 语言") 和 HTML5 编程。
 | ||
| 
 | ||
| * Fundamental concepts of [Angular app design](guide/architecture "Introduction to Angular app-design concepts").
 | ||
| 
 | ||
|   [Angular 应用设计](guide/architecture "Angular 应用设计概念简介")的基本概念。
 | ||
| 
 | ||
| * Basic knowledge of [reactive forms](guide/reactive-forms "Reactive forms guide").
 | ||
| 
 | ||
|   [响应式表单](guide/reactive-forms "反应表单指南")的基础知识。
 | ||
| 
 | ||
| ## Enable reactive forms for your project
 | ||
| 
 | ||
| ## 为项目启用响应式表单
 | ||
| 
 | ||
| Dynamic forms are based on reactive forms. To give the application access reactive forms directives, the [root module](guide/bootstrapping "Learn about bootstrapping an app from the root module.") imports `ReactiveFormsModule` from the `@angular/forms` library.
 | ||
| 
 | ||
| 动态表单是基于响应式表单的。为了让应用访问响应式表达式指令,[根模块会](guide/bootstrapping "要学习如何从根模块启动一个应用。")从 `@angular/forms` 库中导入 `ReactiveFormsModule`。
 | ||
| 
 | ||
| The following code from the example shows the setup in the root module.
 | ||
| 
 | ||
| 以下代码展示了此范例在根模块中所做的设置。
 | ||
| 
 | ||
| <code-tabs>
 | ||
| 
 | ||
|   <code-pane header="app.module.ts" path="dynamic-form/src/app/app.module.ts">
 | ||
| 
 | ||
|   </code-pane>
 | ||
| 
 | ||
|   <code-pane header="main.ts" path="dynamic-form/src/main.ts">
 | ||
| 
 | ||
|   </code-pane>
 | ||
| 
 | ||
| </code-tabs>
 | ||
| 
 | ||
| {@a object-model}
 | ||
| 
 | ||
| ## Create a form object model
 | ||
| 
 | ||
| ## 创建一个表单对象模型
 | ||
| 
 | ||
| A dynamic form requires an object model that can describe all scenarios needed by the form functionality.
 | ||
| The example hero-application form is a set of questions—that is, each control in the form must ask a question and accept an answer.
 | ||
| 
 | ||
| 动态表单需要一个对象模型来描述此表单功能所需的全部场景。英雄应用表单中的例子是一组问题 - 也就是说,表单中的每个控件都必须提问并接受一个答案。
 | ||
| 
 | ||
| The data model for this type of form must represent a question.
 | ||
| The example includes the `DynamicFormQuestionComponent`, which defines a  question as the fundamental object in the model.
 | ||
| 
 | ||
| 此类表单的数据模型必须能表示一个问题。本例中包含 `DynamicFormQuestionComponent`,它定义了一个问题作为模型中的基本对象。
 | ||
| 
 | ||
| The following `QuestionBase` is a base class for a set of controls that can represent the question and its answer in the form.
 | ||
| 
 | ||
| 这个 `QuestionBase` 是一组控件的基类,可以在表单中表示问题及其答案。
 | ||
| 
 | ||
| <code-example path="dynamic-form/src/app/question-base.ts" header="src/app/question-base.ts">
 | ||
| 
 | ||
| </code-example>
 | ||
| 
 | ||
| ### Define control classes
 | ||
| 
 | ||
| ### 定义控件类
 | ||
| 
 | ||
| From this base, the example derives two new classes, `TextboxQuestion` and `DropdownQuestion`,
 | ||
| that represent different control types.
 | ||
| When you create the form template in the next step, you will instantiate these specific question types in order to render the appropriate controls dynamically.
 | ||
| 
 | ||
| 此范例从这个基类派生出两个新类,`TextboxQuestion` 和 `DropdownQuestion`,分别代表不同的控件类型。当你在下一步中创建表单模板时,你会实例化这些具体的问题类,以便动态渲染相应的控件。
 | ||
| 
 | ||
| * The `TextboxQuestion` control type presents a question and allows users to enter input.
 | ||
| 
 | ||
|   `TextboxQuestion` 控件类型表示一个普通问题,并允许用户输入答案。
 | ||
| 
 | ||
|    <code-example path="dynamic-form/src/app/question-textbox.ts" header="src/app/question-textbox.ts"></code-example>
 | ||
| 
 | ||
|    The `TextboxQuestion` control type will be represented in a form template using an `<input>` element.
 | ||
|    The `type` attribute of the element will be defined based on the `type` field specified in the `options` argument (for example `text`, `email`, `url`).
 | ||
| 
 | ||
|   `TextboxQuestion` 控件类型将使用 `<input>` 元素表示在表单模板中。该元素的 `type` 属性将根据 `options` 参数中指定的 `type` 字段定义(例如 `text`,`email`,`url` )。
 | ||
| 
 | ||
| * The `DropdownQuestion` control presents a list of choices in a select box.
 | ||
| 
 | ||
|   `DropdownQuestion` 控件表示在选择框中的一个选项列表。
 | ||
| 
 | ||
|    <code-example path="dynamic-form/src/app/question-dropdown.ts" header="src/app/question-dropdown.ts"></code-example>
 | ||
| 
 | ||
| ### Compose form groups
 | ||
| 
 | ||
| ### 编写表单组
 | ||
| 
 | ||
| A dynamic form uses a service to create grouped sets of input controls, based on the form model.
 | ||
| The following `QuestionControlService` collects a set of `FormGroup` instances that consume the metadata from the question model. You can specify default values and validation rules.
 | ||
| 
 | ||
| 动态表单会使用一个服务来根据表单模型创建输入控件的分组集合。下面的 `QuestionControlService` 会收集一组 `FormGroup` 实例,这些实例会消费问题模型中的元数据。你可以指定一些默认值和验证规则。
 | ||
| 
 | ||
| <code-example path="dynamic-form/src/app/question-control.service.ts" header="src/app/question-control.service.ts"></code-example>
 | ||
| 
 | ||
| {@a form-component}
 | ||
| 
 | ||
| ## Compose dynamic form contents
 | ||
| 
 | ||
| ## 编写动态表单内容
 | ||
| 
 | ||
| The dynamic form itself will be represented by a container component, which you will add in a later step.
 | ||
| Each question is represented in the form component's template by an `<app-question>` tag, which matches an instance of `DynamicFormQuestionComponent`.
 | ||
| 
 | ||
| 动态表单本身就是一个容器组件,稍后你会添加它。每个问题都会在表单组件的模板中用一个 `<app-question>` 标签表示,该标签会匹配 `DynamicFormQuestionComponent` 中的一个实例。
 | ||
| 
 | ||
| The `DynamicFormQuestionComponent` is responsible for rendering the details of an individual question based on values in the data-bound question object.
 | ||
| The form relies on a [`[formGroup]` directive](api/forms/FormGroupDirective "API reference") to connect the template HTML to the underlying control objects.
 | ||
| The `DynamicFormQuestionComponent` creates form groups and populates them with controls defined in the question model, specifying display and validation rules.
 | ||
| 
 | ||
| `DynamicFormQuestionComponent` 负责根据数据绑定的问题对象中的各种值来渲染单个问题的详情。该表单依靠 [`[formGroup]` 指令](api/forms/FormGroupDirective "API 参考")来将模板 HTML 和底层的控件对象联系起来。`DynamicFormQuestionComponent` 会创建表单组,并用问题模型中定义的控件来填充它们,并指定显示和验证规则。
 | ||
| 
 | ||
| <code-tabs>
 | ||
| 
 | ||
|   <code-pane header="dynamic-form-question.component.html" path="dynamic-form/src/app/dynamic-form-question.component.html">
 | ||
| 
 | ||
|   </code-pane>
 | ||
| 
 | ||
|   <code-pane header="dynamic-form-question.component.ts" path="dynamic-form/src/app/dynamic-form-question.component.ts">
 | ||
| 
 | ||
|   </code-pane>
 | ||
| 
 | ||
| </code-tabs>
 | ||
| 
 | ||
| The goal of the `DynamicFormQuestionComponent` is to present question types defined in your model.
 | ||
| You only have two types of questions at this point but you can imagine many more.
 | ||
| The `ngSwitch` statement in the template determines which type of question to display.
 | ||
| The switch uses directives with the [`formControlName`](api/forms/FormControlName "FormControlName directive API reference") and [`formGroup`](api/forms/FormGroupDirective "FormGroupDirective API reference") selectors. Both directives are defined in `ReactiveFormsModule`.
 | ||
| 
 | ||
| `DynamicFormQuestionComponent` 的目标是展示模型中定义的各类问题。你现在只有两类问题,但可以想象将来还会有更多。模板中的 `ngSwitch` 语句会决定要显示哪种类型的问题。这里用到了带有 [`formControlName`](api/forms/FormControlName "FormControlName 指令的 API Reference 参考") 和[`formGroup`](api/forms/FormGroupDirective "FormGroupDirective API 参考指南") 选择器的指令。这两个指令都是在 `ReactiveFormsModule` 中定义的。
 | ||
| 
 | ||
| {@a questionnaire-data}
 | ||
| 
 | ||
| {@a surveyire-data}
 | ||
| 
 | ||
| ### Supply data
 | ||
| 
 | ||
| ### 提供数据
 | ||
| 
 | ||
| Another service is needed to supply a specific set of questions from which to build an individual form.
 | ||
| For this exercise you will create the `QuestionService` to supply this array of questions from the hard-coded sample data.
 | ||
| In a real-world app, the service might fetch data from a backend system.
 | ||
| The key point, however, is that you control the hero job-application questions entirely through the objects returned from `QuestionService`.
 | ||
| To maintain the questionnaire as requirements change, you only need to add, update, and remove objects from the `questions` array.
 | ||
| 
 | ||
| 还要另外一项服务来提供一组具体的问题,以便构建出一个单独的表单。在本练习中,你将创建 `QuestionService` 以从硬编码的范例数据中提供这组问题。在真实世界的应用中,该服务可能会从后端获取数据。重点是,你可以完全通过 `QuestionService` 返回的对象来控制英雄的求职申请问卷。要想在需求发生变化时维护问卷,你只需要在 `questions` 数组中添加、更新和删除对象。
 | ||
| 
 | ||
| The `QuestionService` supplies a set of questions in the form of an array bound to `@Input()` questions.
 | ||
| 
 | ||
| `QuestionService` 以一个绑定到 `@Input()` 的问题数组的形式提供了一组问题。
 | ||
| 
 | ||
| <code-example path="dynamic-form/src/app/question.service.ts" header="src/app/question.service.ts">
 | ||
| 
 | ||
| </code-example>
 | ||
| 
 | ||
| {@a dynamic-template}
 | ||
| 
 | ||
| ## Create a dynamic form template
 | ||
| 
 | ||
| ## 创建一个动态表单模板
 | ||
| 
 | ||
| The `DynamicFormComponent` component is the entry point and the main container for the form, which is represented using the `<app-dynamic-form>` in a template.
 | ||
| 
 | ||
| `DynamicFormComponent` 组件是表单的入口点和主容器,它在模板中用 `<app-dynamic-form>` 表示。
 | ||
| 
 | ||
| The `DynamicFormComponent` component presents a list of questions by binding each one to an `<app-question>` element that matches the `DynamicFormQuestionComponent`.
 | ||
| 
 | ||
| `DynamicFormComponent` 组件通过把每个问题都绑定到一个匹配 `DynamicFormQuestionComponent` 的 `<app-question>` 元素来渲染问题列表。
 | ||
| 
 | ||
| <code-tabs>
 | ||
| 
 | ||
|   <code-pane header="dynamic-form.component.html" path="dynamic-form/src/app/dynamic-form.component.html">
 | ||
| 
 | ||
|   </code-pane>
 | ||
| 
 | ||
|   <code-pane header="dynamic-form.component.ts" path="dynamic-form/src/app/dynamic-form.component.ts">
 | ||
| 
 | ||
|   </code-pane>
 | ||
| 
 | ||
| </code-tabs>
 | ||
| 
 | ||
| ### Display the form
 | ||
| 
 | ||
| ### 显示表单
 | ||
| 
 | ||
| To display an instance of the dynamic form, the `AppComponent` shell template passes the `questions` array returned by the `QuestionService` to the form container component, `<app-dynamic-form>`.
 | ||
| 
 | ||
| 要显示动态表单的一个实例,`AppComponent` 外壳模板会把一个 `QuestionService` 返回的 `questions` 数组传给表单容器组件 `<app-dynamic-form>`。
 | ||
| 
 | ||
| <code-example path="dynamic-form/src/app/app.component.ts" header="app.component.ts">
 | ||
| 
 | ||
| </code-example>
 | ||
| 
 | ||
| The example provides a model for a job application for heroes, but there are
 | ||
| no references to any specific hero question other than the objects returned by `QuestionService`.
 | ||
| This separation of model and data allows you to repurpose the components for any type of survey
 | ||
| as long as it's compatible with the *question* object model.
 | ||
| 
 | ||
| 这个例子为英雄提供了一个工作申请表的模型,但是除了 `QuestionService` 返回的对象外,没有涉及任何跟英雄有关的问题。这种模型和数据的分离,允许你为任何类型的调查表复用这些组件,只要它与这个*问题*对象模型兼容即可。
 | ||
| 
 | ||
| ### Ensuring valid data
 | ||
| 
 | ||
| ### 确保数据有效
 | ||
| 
 | ||
| The form template uses dynamic data binding of metadata to render the form
 | ||
| without making any hardcoded assumptions about specific questions.
 | ||
| It adds both control metadata and validation criteria dynamically.
 | ||
| 
 | ||
| 表单模板使用元数据的动态数据绑定来渲染表单,而不用做任何与具体问题有关的硬编码。它动态添加了控件元数据和验证标准。
 | ||
| 
 | ||
| To ensure valid input, the *Save* button is disabled until the form is in a valid state.
 | ||
| When the form is valid, you can click *Save* and the app renders the current form values as JSON.
 | ||
| 
 | ||
| 要确保输入有效,*就要*禁用 *“Save”* 按钮,直到此表单处于有效状态。当表单有效时,你可以单击 *“Save”* 按钮,该应用就会把表单的当前值渲染为 JSON。
 | ||
| 
 | ||
| The following figure shows the final form.
 | ||
| 
 | ||
| 最终的表单如下图所示。
 | ||
| 
 | ||
| <div class="lightbox">
 | ||
|   <img src="generated/images/guide/dynamic-form/dynamic-form.png" alt="Dynamic-Form">
 | ||
| </div>
 | ||
| 
 | ||
| ## Next steps
 | ||
| 
 | ||
| ## 下一步
 | ||
| 
 | ||
| * **Different types of forms and control collection**
 | ||
| 
 | ||
|   **不同类型的表单和控件集合**
 | ||
| 
 | ||
|    This tutorial shows how to build a a questionaire, which is just one kind of dynamic form.
 | ||
|    The example uses `FormGroup` to collect a set of controls.
 | ||
|    For an example of a different type of dynamic form, see the section [Creating dynamic forms](guide/reactive-forms#creating-dynamic-forms "Create dynamic forms with arrays") in the Reactive Forms guide.
 | ||
|    That example also shows how to use `FormArray` instead of `FormGroup` to collect a set of controls.
 | ||
| 
 | ||
|   本教程展示了如何构建一个问卷,它只是一种动态表单。这个例子使用 `FormGroup` 来收集一组控件。关于不同类型动态表单的范例,请参阅在响应式表单中的[创建动态表单](guide/reactive-forms#creating-dynamic-forms "用数组创建动态表单")一节。那个例子还展示了如何使用 `FormArray` 而不是 `FormGroup` 来收集一组控件。
 | ||
| 
 | ||
| * **Validating user input**
 | ||
| 
 | ||
|   **验证用户输入**
 | ||
| 
 | ||
|    The section [Validating form input](guide/reactive-forms#validating-form-input "Basic input validation") introduces the basics of how input validation works in reactive forms.
 | ||
| 
 | ||
|   [验证表单输入](guide/reactive-forms#validating-form-input "基本输入验证")部分介绍了如何在响应式表单中进行输入验证的基础知识。
 | ||
| 
 | ||
|    The [Form validation guide](guide/form-validation "Form validation guide") covers the topic in more depth.
 | ||
| 
 | ||
|   [表单验证指南](guide/form-validation "表单验证指南")更深入地介绍了本主题。
 | ||
| 
 |