angular-cn/aio/content/guide/dynamic-form.md

230 lines
7.0 KiB
Markdown
Raw Normal View History

@title
Dynamic Forms
@intro
Render dynamic forms with FormGroup.
@description
2017-03-31 19:57:13 -04:00
{@a top}
2017-03-31 19:57:13 -04:00
Building handcrafted forms can be costly and time-consuming,
especially if you 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 describes the business object model.
This cookbook shows you how to use `formGroup` 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.
The example in this cookbook is a dynamic form to build an
online application experience for heroes seeking employment.
The agency is constantly tinkering with the application process.
You can create the forms on the fly *without changing the application code*.
2017-03-31 19:57:13 -04:00
{@a toc}
2017-05-04 12:46:43 -04:00
<!--
# Contents
* [Bootstrap](guide/dynamic-form#bootstrap)
* [Question model](guide/dynamic-form#object-model)
* [Question form components](guide/dynamic-form#form-component)
* [Questionnaire data](guide/dynamic-form#questionnaire-data)
* [Dynamic template](guide/dynamic-form#dynamic-template)
2017-05-04 12:46:43 -04:00
-->
See the <live-example name="dynamic-form"></live-example>.
2017-03-31 19:57:13 -04:00
{@a bootstrap}
## Bootstrap
Start by creating an `NgModule` called `AppModule`.
This cookbook uses [reactive forms](guide/reactive-forms).
Reactive forms belongs to a different `NgModule` called `ReactiveFormsModule`,
so in order to access any reactive forms directives, you have to import
`ReactiveFormsModule` from the `@angular/forms` library.
Bootstrap the `AppModule` in `main.ts`.
<code-tabs>
<code-pane title="app.module.ts" path="dynamic-form/src/app/app.module.ts">
</code-pane>
<code-pane title="main.ts" path="dynamic-form/src/main.ts">
</code-pane>
</code-tabs>
2017-03-31 19:57:13 -04:00
{@a object-model}
## Question model
The next 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.
The following `QuestionBase` is a fundamental question class.
<code-example path="dynamic-form/src/app/question-base.ts" title="src/app/question-base.ts">
</code-example>
2017-03-31 19:57:13 -04:00
From this base you can derive 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` supports multiple HTML5 types such as text, email, and url
via the `type` property.
<code-example path="dynamic-form/src/app/question-textbox.ts" title="src/app/question-textbox.ts" linenums="false">
</code-example>
2017-03-31 19:57:13 -04:00
`DropdownQuestion` presents a list of choices in a select box.
<code-example path="dynamic-form/src/app/question-dropdown.ts" title="src/app/question-dropdown.ts" linenums="false">
</code-example>
2017-03-31 19:57:13 -04:00
Next is `QuestionControlService`, a simple service for transforming the questions to a `FormGroup`.
In a nutshell, the form group consumes the metadata from the question model and
allows you to specify default values and validation rules.
<code-example path="dynamic-form/src/app/question-control.service.ts" title="src/app/question-control.service.ts" linenums="false">
</code-example>
2017-03-31 19:57:13 -04:00
{@a form-component}
## Question form components
Now that you have defined the complete model you are ready
to create components to represent the dynamic form.
2017-03-31 19:57:13 -04:00
`DynamicFormComponent` is the entry point and the main container for the form.
<code-tabs>
<code-pane title="dynamic-form.component.html" path="dynamic-form/src/app/dynamic-form.component.html">
</code-pane>
<code-pane title="dynamic-form.component.ts" path="dynamic-form/src/app/dynamic-form.component.ts">
</code-pane>
</code-tabs>
2017-03-31 19:57:13 -04:00
It presents a list of questions, each 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.
<code-tabs>
<code-pane title="dynamic-form-question.component.html" path="dynamic-form/src/app/dynamic-form-question.component.html">
</code-pane>
<code-pane title="dynamic-form-question.component.ts" path="dynamic-form/src/app/dynamic-form-question.component.ts">
</code-pane>
</code-tabs>
2017-03-31 19:57:13 -04:00
Notice this component can present any type of question in your model.
You only have two types of questions at this point but you can imagine many more.
The `ngSwitch` determines which type of question to display.
In both components you're relying on Angular's **formGroup** to connect the template HTML to the
underlying control objects, populated from the question model with display and validation rules.
`formControlName` and `formGroup` are directives defined in
`ReactiveFormsModule`. The templates can access these directives
directly since you imported `ReactiveFormsModule` from `AppModule`.
{@a questionnaire-data}
2017-03-31 19:57:13 -04:00
## Questionnaire data
`DynamicFormComponent` expects the list of questions in the form of an array bound to `@Input() questions`.
The set of questions you've defined for the job application is returned from the `QuestionService`.
In a real app you'd retrieve these questions from storage.
The key point is that you 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.
<code-example path="dynamic-form/src/app/question.service.ts" title="src/app/question.service.ts">
</code-example>
2017-03-31 19:57:13 -04:00
Finally, display an instance of the form in the `AppComponent` shell.
<code-example path="dynamic-form/src/app/app.component.ts" title="app.component.ts">
</code-example>
2017-03-31 19:57:13 -04:00
{@a dynamic-template}
## Dynamic Template
Although in this example you're modelling a job application for heroes, there are
no references to any specific hero question
outside the objects returned by `QuestionService`.
This is very important since it allows you to repurpose the components for any type of survey
as long as it's compatible with the *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, you are also adding validation dynamically.
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.
This proves that any user input is bound back to the data model.
Saving and retrieving the data is an exercise for another time.
2017-03-31 19:57:13 -04:00
The final form looks like this:
docs(aio): image sweep (#16609) * fix(aio): allow code blocks to clear floated images Previously the negative margin on the code headings were causing floated images to overlay the start of a code block. Now all code block successfully clear all floated elements. * feat(aio): add a `.clear` class for clearing floating images * fix(aio): tidy up image styles The css rules for `img.right` and `img.left` allow authors easy access to floating an image on the left or right, respectively. The `.image-display` rule which was always found on a figure has been simplified so that all figures have this styling. It is very unlikely that a figure will be used outside the content area; and at this time it seems like `figure` is as good an indicator that we want this kind of styling as anything. Now that images are all tagged with width and height values, we cannot assume to modify these dimensions via CSS as it can cause the image to lose its correct proportions. Until we find a better solition we must set `height` to `auto` when the screen width is below 1300px to ensure that these images maintain their proportions as they get shrunk to fit. * docs(aio): general tidy up of image HTML in guides Previously, the guides have a lot of inline image styling and unnecessary use of the `image-display` css class. Images over 700px are problematic for guide docs, so those have been given specific widths and associated heights. * docs(aio): use correct anchor for "back to the top" link The `#toc` anchor does not work when the page is wide enough that the TOC is floating to the side. * build(aio): add `#top-of-page` to path variants for link checking Since the `#top-of-page` is outside the rendered docs the `checkAnchorLinks` processor doesn't find them as valid targets for links. Adding them as a `pathVariant` solves this problem but will still catch links to docs that do not actually exist. * fix(aio): ensure that headings clear floated images * fix(aio): do not force live-example embedded image to 100% size This made them look too big, generally. Leaving them with no size means that they will look reasonable in large viewports and switch to 100% width in narrow viewports.
2017-05-09 18:53:32 -04:00
<figure>
<img src="generated/images/guide/dynamic-form/dynamic-form.png" alt="Dynamic-Form">
</figure>
2017-03-31 19:57:13 -04:00
[Back to top](guide/dynamic-form#top)