0% found this document useful (0 votes)
40 views11 pages

Dynamic Forms - Ts - COOKBOOK

The document describes how to dynamically render forms using Angular's reactive forms module. It defines question models for different control types and a service to transform questions into a form group. Components are created to contain the form and represent individual questions.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
40 views11 pages

Dynamic Forms - Ts - COOKBOOK

The document describes how to dynamically render forms using Angular's reactive forms module. It defines question models for different control types and a service to transform questions into a form group. Components are created to contain the form and represent individual questions.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 11

11/20/2016 Dynamic 

Forms ­ ts ­ COOKBOOK

DYNAMIC FORMS

Render dynamic forms with FormGroup

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 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.

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 ��y without changing our application code.

Table of contents
Bootstrap

Question Model

https://angular.io/docs/ts/latest/cookbook/dynamic­form.html 1/11
11/20/2016 Dynamic Forms ­ ts ­ COOKBOOK

Form Component

Questionnaire Metadata

Dynamic Template

See the live example.

Bootstrap
We start by creating an NgModule called AppModule .

In our example we will be using Reactive Forms.

Reactive Forms belongs to a different NgModule called ReactiveFormsModule ,


so in order to access any Reactive Forms directives, we have to import
ReactiveFormsModule from AppModule .

We bootstrap our AppModule in main.ts.

1. import { BrowserModule } from '@angular/platform-


browser';
2. import { ReactiveFormsModule } from '@angular/forms';
3. import { NgModule } from '@angular/core';
4.

5. import { AppComponent } from './app.component';


6. import { DynamicFormComponent } from './dynamic-
form.component';
7. import { DynamicFormQuestionComponent } from './dynamic-form-
question.component';
8.

9. @NgModule({
10. imports: [ BrowserModule, ReactiveFormsModule ],

https://angular.io/docs/ts/latest/cookbook/dynamic­form.html 2/11
11/20/2016 Dynamic Forms ­ ts ­ COOKBOOK

11. declarations: [ AppComponent, DynamicFormComponent,


DynamicFormQuestionComponent ],
12. bootstrap: [ AppComponent ]
13. })
14. export class AppModule {
15. constructor() {
16. }
17. }

Question Model
The next step is to de��ne 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.

app/question-base.ts

1. export class QuestionBase<T>{


2. value: T;
3. key: string;
4. label: string;
5. required: boolean;
6. order: number;
7. controlType: string;
8.

9. constructor(options: {
10. value?: T,
11. key?: string,
12. label?: string,
13. required?: boolean,
14. order?: number,
https://angular.io/docs/ts/latest/cookbook/dynamic­form.html 3/11
11/20/2016 Dynamic Forms ­ ts ­ COOKBOOK

15. controlType?: string


16. } = {}) {
17. this.value = options.value;
18. this.key = options.key || '';
19. this.label = options.label || '';
20. this.required = !!options.required;
21. this.order = options.order === undefined ? 1 : options.order;
22. this.controlType = options.controlType || '';
23. }
24. }

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 speci��c question types and render the appropriate
controls dynamically.

TextboxQuestion supports multiple html5 types like text, email, url etc via the

type property.

app/question-textbox.ts

import { QuestionBase } from './question-base';

export class TextboxQuestion extends QuestionBase<string> {


controlType = 'textbox';
type: string;

constructor(options: {} = {}) {
super(options);
this.type = options['type'] || '';
}
}

https://angular.io/docs/ts/latest/cookbook/dynamic­form.html 4/11
11/20/2016 Dynamic Forms ­ ts ­ COOKBOOK

DropdownQuestion presents a list of choices in a select box.

app/question-dropdown.ts

import { QuestionBase } from './question-base';

export class DropdownQuestion extends QuestionBase<string> {


controlType = 'dropdown';
options: {key: string, value: string}[] = [];

constructor(options: {} = {}) {
super(options);
this.options = options['options'] || [];
}
}

Next we have de��ned QuestionControlService , a simple service for


transforming our 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.

app/question-control.service.ts

import { Injectable } from '@angular/core';


import { FormControl, FormGroup, Validators } from
'@angular/forms';

import { QuestionBase } from './question-base';

@Injectable()
export class QuestionControlService {
constructor() { }

toFormGroup(questions: QuestionBase<any>[] ) {

https://angular.io/docs/ts/latest/cookbook/dynamic­form.html 5/11
11/20/2016 Dynamic Forms ­ ts ­ COOKBOOK

let group: any = {};

questions.forEach(question => {
group[question.key] = question.required ? new
FormControl(question.value || '', Validators.required)
: new
FormControl(question.value || '');
});
return new FormGroup(group);
}
}

Question form components


Now that we have de��ned the complete model we are ready to create components
to represent the dynamic form.

DynamicFormComponent is the entry point and the main container for the form.

1. <div>
2. <form (ngSubmit)="onSubmit()" [formGroup]="form">
3.

4. <div *ngFor="let question of questions" class="form-row">


5. <df-question [question]="question" [form]="form"></df-
question>
6. </div>
7.

8. <div class="form-row">
9. <button type="submit" [disabled]="!form.valid">Save</button>
10. </div>
11. </form>
12.

13. <div *ngIf="payLoad" class="form-row">

https://angular.io/docs/ts/latest/cookbook/dynamic­form.html 6/11
11/20/2016 Dynamic Forms ­ ts ­ COOKBOOK

14. <strong>Saved the following values</strong><br>{{payLoad}}


15. </div>
16. </div>

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.

1. <div [formGroup]="form">
2. <label [attr.for]="question.key">{{question.label}}</label>
3.

4. <div [ngSwitch]="question.controlType">
5.

6. <input *ngSwitchCase="'textbox'"
[formControlName]="question.key"
7. [id]="question.key" [type]="question.type">
8.

9. <select [id]="question.key" *ngSwitchCase="'dropdown'"


[formControlName]="question.key">
10. <option *ngFor="let opt of question.options"
[value]="opt.key">{{opt.value}}</option>
11. </select>
12.

13. </div>
14.

15. <div class="errorMessage" *ngIf="!isValid">{{question.label}} is


required</div>
16. </div>

https://angular.io/docs/ts/latest/cookbook/dynamic­form.html 7/11
11/20/2016 Dynamic Forms ­ ts ­ COOKBOOK

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 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 de��ned in

ReactiveFormsModule . Our templates can can access these directives directly

since we imported ReactiveFormsModule from AppModule .

Questionnaire data
DynamicFormComponent expects the list of questions in the form of an array

bound to @Input() questions .

The set of questions we have de��ned 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.

app/question.service.ts

1. import { Injectable } from '@angular/core';


2.

3. import { DropdownQuestion } from './question-dropdown';


4. import { QuestionBase } from './question-base';
5. import { TextboxQuestion } from './question-textbox';
6.

https://angular.io/docs/ts/latest/cookbook/dynamic­form.html 8/11
11/20/2016 Dynamic Forms ­ ts ­ COOKBOOK

7. @Injectable()
8. export class QuestionService {
9.

10. // Todo: get from a remote source of question metadata


11. // Todo: make asynchronous
12. getQuestions() {
13.

14. let questions: QuestionBase<any>[] = [


15.

16. new DropdownQuestion({


17. key: 'brave',
18. label: 'Bravery Rating',
19. options: [
20. {key: 'solid', value: 'Solid'},
21. {key: 'great', value: 'Great'},
22. {key: 'good', value: 'Good'},
23. {key: 'unproven', value: 'Unproven'}
24. ],
25. order: 3
26. }),
27.

28. new TextboxQuestion({


29. key: 'firstName',
30. label: 'First name',
31. value: 'Bombasto',
32. required: true,
33. order: 1
34. }),
35.

36. new TextboxQuestion({


37. key: 'emailAddress',
38. label: 'Email',
39. type: 'email',
40. order: 2
41. })
42. ];
43.

44. return questions.sort((a, b) => a.order - b.order);

https://angular.io/docs/ts/latest/cookbook/dynamic­form.html 9/11
11/20/2016 Dynamic Forms ­ ts ­ COOKBOOK

45. }
46. }

Finally, we display an instance of the form in the AppComponent shell.

app.component.ts

1. import { Component } from '@angular/core';


2.

3. import { QuestionService } from './question.service';


4.

5. @Component({
6. selector: 'my-app',
7. template: `
8. <div>
9. <h2>Job Application for Heroes</h2>
10. <dynamic-form [questions]="questions"></dynamic-form>
11. </div>
12. `,
13. providers: [QuestionService]
14. })
15. export class AppComponent {
16. questions: any[];
17.

18. constructor(service: QuestionService) {


19. this.questions = service.getQuestions();
20. }
21. }

Dynamic Template

https://angular.io/docs/ts/latest/cookbook/dynamic­form.html 10/11
11/20/2016 Dynamic Forms ­ ts ­ COOKBOOK

Although in this example we're modelling a job application for heroes, there are no
references to any speci��c 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 speci��c 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.

The ��nal form looks like this:

Back to top

https://angular.io/docs/ts/latest/cookbook/dynamic­form.html 11/11

You might also like