QuestionsModule:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';
import { ReactiveFormsModule } from '@angular/forms';
import { DynamicHostComponent } from './dynamic-host.component';
import { DynamicFormComponent } from './dynamic-form.component';
import { DynamicFormQuestionComponent } from './dynamic-form-question.component';
const routes: Routes = [
{ path: 'questions', component: DynamicHostComponent }
];
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
RouterModule.forChild(routes)
],
declarations: [
DynamicHostComponent,
DynamicFormComponent,
DynamicFormQuestionComponent
]
})
export class QuestionsModule { }
QuestionService:
import { Injectable } from '@angular/core';
import { DropdownQuestion } from './question-dropdown';
import { QuestionBase } from './question-base';
import { TextboxQuestion } from './question-textbox';
@Injectable()
export class QuestionService {
// todo: get from a remote source of question metadata
// todo: make asynchronous
getQuestions() {
let questions: QuestionBase<any>[] = [
new DropdownQuestion({
key: 'power',
label: 'Technology Power',
options: [
{ key: 'dataBinding', value: 'Data Binding' },
{ key: 'animations', value: 'Animations' },
{ key: 'tooling', value: 'Tooling' },
{ key: 'beautifulCSS', value: 'Beautiful CSS'}
],
order: 3
}),
new TextboxQuestion({
key: 'name',
label: 'Name',
value: 'Angular',
required: true,
order: 1
}),
new TextboxQuestion({
key: 'url',
label: 'URL',
type: 'url',
order: 2
})
];
return questions.sort((a, b) => a.order - b.order);
}
}
QuestionControlService:
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>[]) {
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);
}
}
QuestionBase:
export class QuestionBase<T> {
value: T;
key: string;
label: string;
required: boolean;
order: number;
controlType: string;
constructor(options: {
value?: T,
key?: string,
label?: string,
required?: boolean,
order?: number,
controlType?: string
} = {}) {
this.value = options.value;
this.key = options.key || '';
this.label = options.label || '';
this.required = !!options.required;
this.order = options.order === undefined ? 1 : options.order;
this.controlType = options.controlType || '';
}
}
TextboxQuestion:
import { QuestionBase } from './question-base';
export class TextboxQuestion extends QuestionBase<string> {
controlType = 'textbox';
type: string;
constructor(options: {} = {}){
super(options);
this.type = options['type'] || '';
}
}
DropdownQuestion:
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'] || [];
}
}
import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { QuestionBase } from './question-base';
@Component({
selector: 'app-dynamic-form-question',
template: `
<div [formGroup]="form">
<label [attr.for]="question.key">{{ question.label }}</label>
<div [ngSwitch]="question.controlType">
<input *ngSwitchCase="'textbox'" [formControlName]="question.key"
[id]="question.key" [type]="question.type">
<select [id]="question.key" *ngSwitchCase="'dropdown'"
[formControlName]="question.key">
<option *ngFor="let opt of question.options" [value]="opt.key">
{{ opt.value }}</option>
</select>
</div>
<div class="errorMessage" *ngIf="!isValid">{{ question.label }} is required</div>
</div>
`
})
export class DynamicFormQuestionComponent {
@Input() question: QuestionBase<any>;
@Input() form: FormGroup;
get isValid() { return this.form.controls[this.question.key].valid; }
}
import { Component, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { QuestionBase } from './question-base';
import { QuestionControlService } from './question-control.service';
@Component({
selector: 'app-dynamic-form',
template: `
<div>
<form (ngSubmit)="onSubmit()" [formGroup]="form">
<div *ngFor="let question of questions" class="form-row">
<app-dynamic-form-question [question]="question" [form]="form">
</app-dynamic-form-question>
</div>
<div class="form-row">
<button type="submit" [disabled]="!form.valid">Save</button>
</div>
</form>
<div *ngIf="payLoad" class="form-row">
<strong>Saved the following values</strong>
<br>
{{ payLoad }}
</div>
</div>
`,
providers: [ QuestionControlService ]
})
export class DynamicFormComponent {
@Input() questions: QuestionBase<any>[] = [];
form: FormGroup;
payLoad = '';
constructor(private questionControlService: QuestionControlService) { }
ngOnInit() {
this.form = this.questionControlService.toFormGroup(this.questions);
}
onSubmit() {
this.payLoad = JSON.stringify(this.form.value);
}
}
DynamicHostComponent:
import { Component } from '@angular/core';
import { QuestionService } from './question.service';
@Component({
selector: 'app-dynamic-host',
template: `
<div>
<h2>Technology Power for Angular</h2>
<app-dynamic-form [questions]="questions"></app-dynamic-form>
</div>
`,
providers: [ QuestionService ]
})
export class DynamicHostComponent {
questions: any[];
constructor(private questionService: QuestionService) {
this.questions = questionService.getQuestions();
}
}
Let's have a look at the results in Chromium: