docs: refactor dynamic forms topic as tutorial (#36465)
rework content to meet current documentation standards and conventions, structure as tutorial document PR Close #36465
This commit is contained in:
parent
286fbf42c6
commit
1e208e8c87
|
@ -1,42 +1,53 @@
|
||||||
# Dynamic forms
|
# Building dynamic forms
|
||||||
|
|
||||||
{@a top}
|
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.
|
||||||
|
|
||||||
Building handcrafted forms can be costly and time-consuming,
|
The technique is particularly useful when you have a type of form whose content must
|
||||||
especially if you need a great number of them, they're similar to each other, and they change frequently
|
change frequently to meet rapidly changing business and regulatory requirements.
|
||||||
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.
|
||||||
|
|
||||||
It may be more economical to create the forms dynamically, based on
|
In this tutorial you will build a dynamic form that presents a basic questionaire.
|
||||||
metadata that describes the business object model.
|
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.
|
||||||
|
|
||||||
This cookbook shows you how to use `formGroup` to dynamically
|
The tutorial walks you through the following steps.
|
||||||
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
|
1. Enable reactive forms for a project.
|
||||||
online application experience for heroes seeking employment.
|
2. Establish a data model to represent form controls.
|
||||||
The agency is constantly tinkering with the application process.
|
3. Populate the model with sample data.
|
||||||
You can create the forms on the fly *without changing the application code*.
|
4. Develop a component to create form controls dynamically.
|
||||||
{@a toc}
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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>.
|
See the <live-example name="dynamic-form"></live-example>.
|
||||||
|
|
||||||
{@a bootstrap}
|
</div>
|
||||||
|
|
||||||
## Bootstrap
|
## Prerequisites
|
||||||
|
|
||||||
Start by creating an `NgModule` called `AppModule`.
|
Before doing this tutorial, you should have a basic understanding to the following.
|
||||||
|
|
||||||
This cookbook uses [reactive forms](guide/reactive-forms).
|
* [TypeScript](https://www.typescriptlang.org/docs/home.html "The TypeScript language") and HTML5 programming.
|
||||||
|
|
||||||
Reactive forms belongs to a different `NgModule` called `ReactiveFormsModule`,
|
* Fundamental concepts of [Angular app design](guide/architecture "Introduction to Angular app-design concepts").
|
||||||
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`.
|
* Basic knowledge of [reactive forms](guide/reactive-forms "Reactive forms guide").
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
The following code from the example shows the setup in the root module.
|
||||||
|
|
||||||
<code-tabs>
|
<code-tabs>
|
||||||
|
|
||||||
|
@ -50,79 +61,56 @@ Bootstrap the `AppModule` in `main.ts`.
|
||||||
|
|
||||||
</code-tabs>
|
</code-tabs>
|
||||||
|
|
||||||
|
|
||||||
{@a object-model}
|
{@a object-model}
|
||||||
|
|
||||||
## Question model
|
## Create a form object model
|
||||||
|
|
||||||
The next step is to define an object model that can describe all scenarios needed by the form functionality.
|
A dynamic form requires 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 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 _question_ is the most fundamental object in the model.
|
|
||||||
|
|
||||||
The following `QuestionBase` is a fundamental question class.
|
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.
|
||||||
|
|
||||||
|
The following `QuestionBase` is a base class for a set of controls that can represent the question and its answer in the form.
|
||||||
|
|
||||||
<code-example path="dynamic-form/src/app/question-base.ts" header="src/app/question-base.ts">
|
<code-example path="dynamic-form/src/app/question-base.ts" header="src/app/question-base.ts">
|
||||||
|
|
||||||
</code-example>
|
</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.
|
||||||
|
|
||||||
From this base you can derive two new classes in `TextboxQuestion` and `DropdownQuestion`
|
* The `TextboxQuestion` control type presents a question and allows users to enter input.
|
||||||
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" header="src/app/question-textbox.ts"></code-example>
|
<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`).
|
||||||
|
|
||||||
|
* The `DropdownQuestion` control presents a list of choices in a select box.
|
||||||
`DropdownQuestion` presents a list of choices in a select box.
|
|
||||||
|
|
||||||
|
|
||||||
<code-example path="dynamic-form/src/app/question-dropdown.ts" header="src/app/question-dropdown.ts"></code-example>
|
<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.
|
||||||
Next is `QuestionControlService`, a simple service for transforming the questions to a `FormGroup`.
|
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.
|
||||||
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" header="src/app/question-control.service.ts"></code-example>
|
<code-example path="dynamic-form/src/app/question-control.service.ts" header="src/app/question-control.service.ts"></code-example>
|
||||||
|
|
||||||
{@a form-component}
|
{@a form-component}
|
||||||
|
|
||||||
## Question form components
|
## Compose dynamic form contents
|
||||||
Now that you have defined the complete model you are ready
|
|
||||||
to create components to represent the dynamic form.
|
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
`DynamicFormComponent` is the entry point and the main container for the form.
|
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.
|
||||||
<code-tabs>
|
The `DynamicFormQuestionComponent` creates form groups and populates them with controls defined in the question model, specifying display and validation rules.
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
It presents a list of questions, each bound to a `<app-question>` component element.
|
|
||||||
The `<app-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-tabs>
|
||||||
|
|
||||||
|
@ -136,70 +124,88 @@ question based on values in the data-bound question object.
|
||||||
|
|
||||||
</code-tabs>
|
</code-tabs>
|
||||||
|
|
||||||
|
The goal of the `DynamicFormQuestionComponent` is to present question types defined in your model.
|
||||||
|
|
||||||
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.
|
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.
|
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`.
|
||||||
|
|
||||||
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}
|
{@a questionnaire-data}
|
||||||
|
|
||||||
## Questionnaire data
|
### Supply data
|
||||||
|
|
||||||
`DynamicFormComponent` expects the list of questions in the form of an array bound to `@Input() questions`.
|
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.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
The `QuestionService` supplies a set of questions in the form of an array bound to `@Input()` questions.
|
||||||
|
|
||||||
<code-example path="dynamic-form/src/app/question.service.ts" header="src/app/question.service.ts">
|
<code-example path="dynamic-form/src/app/question.service.ts" header="src/app/question.service.ts">
|
||||||
|
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
{@a dynamic-template}
|
||||||
|
|
||||||
Finally, display an instance of the form in the `AppComponent` shell.
|
## 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.
|
||||||
|
|
||||||
|
The `DynamicFormComponent` component presents a list of questions by binding each one to an `<app-question>` element that matches the `DynamicFormQuestionComponent`.
|
||||||
|
|
||||||
|
<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>`.
|
||||||
|
|
||||||
<code-example path="dynamic-form/src/app/app.component.ts" header="app.component.ts">
|
<code-example path="dynamic-form/src/app/app.component.ts" header="app.component.ts">
|
||||||
|
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
{@a dynamic-template}
|
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`.
|
||||||
## Dynamic Template
|
This separation of model and data allows you to repurpose the components for any type of survey
|
||||||
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.
|
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
|
|
||||||
|
### Ensuring valid data
|
||||||
|
|
||||||
|
The form template uses dynamic data binding of metadata to render the form
|
||||||
without making any hardcoded assumptions about specific questions.
|
without making any hardcoded assumptions about specific questions.
|
||||||
In addition to control metadata, you are also adding validation dynamically.
|
It adds both control metadata and validation criteria dynamically.
|
||||||
|
|
||||||
The *Save* button is disabled until the form is in a valid state.
|
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.
|
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.
|
|
||||||
|
|
||||||
|
The following figure shows the final form.
|
||||||
The final form looks like this:
|
|
||||||
|
|
||||||
<div class="lightbox">
|
<div class="lightbox">
|
||||||
<img src="generated/images/guide/dynamic-form/dynamic-form.png" alt="Dynamic-Form">
|
<img src="generated/images/guide/dynamic-form/dynamic-form.png" alt="Dynamic-Form">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
|
||||||
[Back to top](guide/dynamic-form#top)
|
* **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.
|
||||||
|
|
||||||
|
* **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.
|
||||||
|
|
||||||
|
The [Form validation guide](guide/form-validation "Form validation guide") covers the topic in more depth.
|
||||||
|
|
|
@ -266,8 +266,8 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/dynamic-form",
|
"url": "guide/dynamic-form",
|
||||||
"title": "Dynamic Forms",
|
"title": "Building Dynamic Forms",
|
||||||
"tooltip": "Render dynamic forms with FormGroup."
|
"tooltip": "Create dynamic form templates using FormGroup."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue