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.  | ||
|  |      | ||
|  |     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](/resources/live-examples/cb-dynamic-form-deprecated/ts/plnkr.html)**. | ||
|  | 
 | ||
|  | .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) |