docs(dynamic-forms): edit copy to guidelines standards (#3525)
This commit is contained in:
parent
0cf98324a8
commit
6a9007c65b
|
@ -1,50 +1,50 @@
|
||||||
include ../_util-fns
|
include ../_util-fns
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
We can't always justify the cost and time to build handcrafted forms,
|
Building handcrafted forms can be costly and time-consuming,
|
||||||
especially if we'll need a great number of them, they're similar to each other, and they change frequently
|
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.
|
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.
|
It may be more economical to create the forms dynamically, based on
|
||||||
|
metadata that describes the business object model.
|
||||||
|
|
||||||
In this cookbook we show how to use `formGroup` to dynamically render a simple form with different control types and validation.
|
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's a primitive start.
|
||||||
It might evolve to support a much richer variety of questions, more graceful rendering, and superior user experience.
|
It might evolve to support a much richer variety of questions, more graceful rendering, and superior user experience.
|
||||||
All such greatness has humble beginnings.
|
All such greatness has humble beginnings.
|
||||||
|
|
||||||
In our example we use a dynamic form to build an online application experience for heroes seeking employment.
|
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.
|
The agency is constantly tinkering with the application process.
|
||||||
We can create the forms on the fly *without changing our application code*.
|
You can create the forms on the fly *without changing the application code*.
|
||||||
|
|
||||||
<a id="toc"></a>
|
<a id="toc"></a>
|
||||||
:marked
|
:marked
|
||||||
## Table of contents
|
# Contents
|
||||||
|
* [Bootstrap](#bootstrap)
|
||||||
[Bootstrap](#bootstrap)
|
* [Question model](#object-model)
|
||||||
|
* [Question form components](#form-component)
|
||||||
[Question Model](#object-model)
|
* [Questionnaire data](#questionnaire-data)
|
||||||
|
* [Dynamic template](#dynamic-template)
|
||||||
[Form Component](#form-component)
|
|
||||||
|
|
||||||
[Questionnaire Metadata](#questionnaire-metadata)
|
|
||||||
|
|
||||||
[Dynamic Template](#dynamic-template)
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
**See the <live-example name="cb-dynamic-form"></live-example>**.
|
See the <live-example name="cb-dynamic-form"></live-example>.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
<a id="bootstrap"></a>
|
<a id="bootstrap"></a>
|
||||||
:marked
|
:marked
|
||||||
## Bootstrap
|
## Bootstrap
|
||||||
|
|
||||||
We start by creating an `NgModule` called `AppModule`.
|
Start by creating an `NgModule` called `AppModule`.
|
||||||
|
|
||||||
In our example we will be using Reactive Forms.
|
This cookbook uses [reactive forms](../guide/reactive-forms.html).
|
||||||
|
|
||||||
Reactive Forms belongs to a different `NgModule` called `ReactiveFormsModule`, so in order to access any Reactive Forms directives, we have to import `ReactiveFormsModule` from the `@angular/forms` library.
|
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.
|
||||||
|
|
||||||
We bootstrap our `AppModule` in main.ts.
|
Bootstrap the `AppModule` in `main.ts`.
|
||||||
|
|
||||||
+makeTabs(
|
+makeTabs(
|
||||||
`cb-dynamic-form/ts/src/app/app.module.ts,
|
`cb-dynamic-form/ts/src/app/app.module.ts,
|
||||||
|
@ -57,21 +57,24 @@ include ../_util-fns
|
||||||
.l-main-section
|
.l-main-section
|
||||||
<a id="object-model"></a>
|
<a id="object-model"></a>
|
||||||
:marked
|
:marked
|
||||||
## Question Model
|
## Question model
|
||||||
|
|
||||||
The next step is to define an object model that can describe all scenarios needed by the form functionality.
|
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 hero application process involves a form with a lot of questions.
|
||||||
The "question" is the most fundamental object in the model.
|
The _question_ is the most fundamental object in the model.
|
||||||
|
|
||||||
We have created `QuestionBase` as the most fundamental question class.
|
The following `QuestionBase` is a fundamental question class.
|
||||||
|
|
||||||
+makeExample('cb-dynamic-form/ts/src/app/question-base.ts','','src/app/question-base.ts')
|
+makeExample('cb-dynamic-form/ts/src/app/question-base.ts','','src/app/question-base.ts')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
From this base we derived two new classes in `TextboxQuestion` and `DropdownQuestion` that represent Textbox and Dropdown questions.
|
From this base you can derive two new classes in `TextboxQuestion` and `DropdownQuestion`
|
||||||
The idea is that the form will be bound to specific question types and render the appropriate controls dynamically.
|
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 like text, email, url etc via the `type` property.
|
`TextboxQuestion` supports multiple HTML5 types such as text, email, and url
|
||||||
|
via the `type` property.
|
||||||
|
|
||||||
+makeExample('cb-dynamic-form/ts/src/app/question-textbox.ts',null,'src/app/question-textbox.ts')(format='.')
|
+makeExample('cb-dynamic-form/ts/src/app/question-textbox.ts',null,'src/app/question-textbox.ts')(format='.')
|
||||||
|
|
||||||
|
@ -81,15 +84,17 @@ include ../_util-fns
|
||||||
+makeExample('cb-dynamic-form/ts/src/app/question-dropdown.ts',null,'src/app/question-dropdown.ts')(format='.')
|
+makeExample('cb-dynamic-form/ts/src/app/question-dropdown.ts',null,'src/app/question-dropdown.ts')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Next we have defined `QuestionControlService`, a simple service for transforming our questions to a `FormGroup`.
|
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 us to 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.
|
||||||
|
|
||||||
+makeExample('cb-dynamic-form/ts/src/app/question-control.service.ts',null,'src/app/question-control.service.ts')(format='.')
|
+makeExample('cb-dynamic-form/ts/src/app/question-control.service.ts',null,'src/app/question-control.service.ts')(format='.')
|
||||||
|
|
||||||
<a id="form-component"></a>
|
<a id="form-component"></a>
|
||||||
:marked
|
:marked
|
||||||
## Question form components
|
## Question form components
|
||||||
Now that we have defined the complete model we are ready to create components to represent the dynamic form.
|
Now that you have defined the complete model you are ready
|
||||||
|
to create components to represent the dynamic form.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
`DynamicFormComponent` is the entry point and the main container for the form.
|
`DynamicFormComponent` is the entry point and the main container for the form.
|
||||||
|
@ -101,9 +106,10 @@ include ../_util-fns
|
||||||
dynamic-form.component.ts`
|
dynamic-form.component.ts`
|
||||||
)
|
)
|
||||||
:marked
|
:marked
|
||||||
It presents a list of questions, each question bound to a `<df-question>` component element.
|
It presents a list of questions, each bound to a `<df-question>` component element.
|
||||||
The `<df-question>` tag matches the `DynamicFormQuestionComponent`,
|
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.
|
the component responsible for rendering the details of each _individual_
|
||||||
|
question based on values in the data-bound question object.
|
||||||
|
|
||||||
+makeTabs(
|
+makeTabs(
|
||||||
`cb-dynamic-form/ts/src/app/dynamic-form-question.component.html,
|
`cb-dynamic-form/ts/src/app/dynamic-form-question.component.html,
|
||||||
|
@ -113,48 +119,53 @@ include ../_util-fns
|
||||||
dynamic-form-question.component.ts`
|
dynamic-form-question.component.ts`
|
||||||
)
|
)
|
||||||
:marked
|
:marked
|
||||||
Notice this component can present any type of question in our model.
|
Notice this component can present any type of question in your model.
|
||||||
We only have two types of questions at this point but we 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` determines which type of question to display.
|
||||||
|
|
||||||
In both components we're relying on Angular's **formGroup** to connect the template HTML to the
|
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.
|
underlying control objects, populated from the question model with display and validation rules.
|
||||||
|
|
||||||
`formControlName` and `formGroup` are directives defined in `ReactiveFormsModule`. Our templates can access these directives directly since we imported `ReactiveFormsModule` from `AppModule`.
|
`formControlName` and `formGroup` are directives defined in
|
||||||
|
`ReactiveFormsModule`. The templates can access these directives
|
||||||
|
directly since you imported `ReactiveFormsModule` from `AppModule`.
|
||||||
|
|
||||||
<a id="questionnaire-metadata"></a>
|
<a id="questionnaire-data"></a>
|
||||||
:marked
|
:marked
|
||||||
## Questionnaire data
|
## Questionnaire data
|
||||||
:marked
|
:marked
|
||||||
`DynamicFormComponent` expects the list of questions in the form of an array bound to `@Input() questions`.
|
`DynamicFormComponent` expects the list of questions in the form of an array bound to `@Input() questions`.
|
||||||
|
|
||||||
The set of questions we have defined for the job application is returned from the `QuestionService`.
|
The set of questions you've defined for the job application is returned from the `QuestionService`.
|
||||||
In a real app we'd retrieve these questions from storage.
|
In a real app you'd retrieve these questions from storage.
|
||||||
|
|
||||||
The key point is that we control the hero job application questions entirely through the objects returned from `QuestionService`.
|
The key point is that you control the hero job application questions
|
||||||
Questionnaire maintenance is a simple matter of adding, updating, and removing objects from the `questions` array.
|
entirely through the objects returned from `QuestionService`.
|
||||||
|
Questionnaire maintenance is a simple matter of adding, updating,
|
||||||
|
and removing objects from the `questions` array.
|
||||||
|
|
||||||
+makeExample('cb-dynamic-form/ts/src/app/question.service.ts','','src/app/question.service.ts')
|
+makeExample('cb-dynamic-form/ts/src/app/question.service.ts','','src/app/question.service.ts')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Finally, we display an instance of the form in the `AppComponent` shell.
|
Finally, display an instance of the form in the `AppComponent` shell.
|
||||||
|
|
||||||
+makeExample('cb-dynamic-form/ts/src/app/app.component.ts','','app.component.ts')
|
+makeExample('cb-dynamic-form/ts/src/app/app.component.ts','','app.component.ts')
|
||||||
|
|
||||||
<a id="dynamic-template"></a>
|
<a id="dynamic-template"></a>
|
||||||
:marked
|
:marked
|
||||||
## Dynamic Template
|
## Dynamic Template
|
||||||
Although in this example we're modelling a job application for heroes, there are no references to any specific hero question
|
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`.
|
outside the objects returned by `QuestionService`.
|
||||||
|
|
||||||
This is very important since it allows us to repurpose the components for any type of survey
|
This is very important since it allows you to repurpose the components for any type of survey
|
||||||
as long as it's compatible with our *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
|
The key is the dynamic data binding of metadata used 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, we are also adding validation dynamically.
|
In addition to control metadata, you are also adding validation dynamically.
|
||||||
|
|
||||||
The *Save* button is disabled until the form is in a valid state.
|
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.
|
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.
|
This proves that any user input is bound back to the data model.
|
||||||
Saving and retrieving the data is an exercise for another time.
|
Saving and retrieving the data is an exercise for another time.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue