150 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			150 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| include ../_util-fns
 | |
| 
 | |
| .alert.is-important
 | |
|   :marked
 | |
|     This cookbook is using the deprecated forms API, which is disabled as of RC5, thus this sample only works up to RC4. 
 | |
|     
 | |
|     We have created a new version of this cookbook using the new API <a href='/docs/ts/latest/cookbook/dynamic-form.html'>here</a>. 
 | |
| 
 | |
| :marked
 | |
|    We can't always justify the cost and time to build handcrafted forms, 
 | |
|    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.
 | |
|   
 | |
|    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*. 
 | |
|    
 | |
| <a id="toc"></a>
 | |
| :marked
 | |
|    ## Table of contents
 | |
| 
 | |
|       [Question Model](#object-model)
 | |
| 
 | |
|       [Form Component](#form-component)
 | |
| 
 | |
|       [Questionnaire Metadata](#questionnaire-metadata)
 | |
|       
 | |
|       [Dynamic Template](#dynamic-template)
 | |
| 
 | |
| :marked
 | |
|    **See the <live-example name="cb-dynamic-form-deprecated"></live-example>**.
 | |
| 
 | |
| .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.
 | |
| 
 | |
| +makeExample('cb-dynamic-form-deprecated/ts/app/question-base.ts','','app/question-base.ts')
 | |
| 
 | |
| :marked
 | |
|    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` supports multiple html5 types like text, email, url etc via the `type` property.
 | |
| 
 | |
| +makeExample('cb-dynamic-form-deprecated/ts/app/question-textbox.ts',null,'app/question-textbox.ts')(format='.')
 | |
| 
 | |
| :marked
 | |
|    `DropdownQuestion` presents a list of choices in a select box.
 | |
|    
 | |
| +makeExample('cb-dynamic-form-deprecated/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.
 | |
| 
 | |
| +makeExample('cb-dynamic-form-deprecated/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
 | |
|   `DynamicFormComponent` is the entry point and the main container for the form. 
 | |
| +makeTabs(
 | |
|   `cb-dynamic-form-deprecated/ts/app/dynamic-form.component.html,
 | |
|    cb-dynamic-form-deprecated/ts/app/dynamic-form.component.ts`,
 | |
|   null,
 | |
|   `dynamic-form.component.html,
 | |
|    dynamic-form.component.ts`
 | |
| )  
 | |
| :marked
 | |
|   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.  
 | |
| 
 | |
| +makeTabs(
 | |
|   `cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.html,
 | |
|    cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.ts`,
 | |
|   null,
 | |
|   `dynamic-form-question.component.html,
 | |
|    dynamic-form-question.component.ts`
 | |
| )
 | |
| :marked
 | |
|   Notice this component can present any type of question in our model. 
 | |
|   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.
 | |
|   
 | |
|   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.
 | |
| 
 | |
| <a id="questionnaire-metadata"></a>
 | |
| :marked
 | |
|    ## Questionnaire data
 | |
| :marked
 | |
|   `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`. 
 | |
|    In a real app we'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`. 
 | |
|    Questionnaire maintenance is a simple matter of adding, updating, and removing objects from the `questions` array.
 | |
|    
 | |
| +makeExample('cb-dynamic-form-deprecated/ts/app/question.service.ts','','app/question.service.ts')  
 | |
| 
 | |
| :marked
 | |
|   Finally, we display an instance of the form in the `AppComponent` shell.
 | |
| 
 | |
| +makeExample('cb-dynamic-form-deprecated/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`. 
 | |
|    
 | |
|    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.
 | |
| 
 | |
| :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)
 |