Page 19 - MSDN Magazine, July 2018
P. 19

Angular thinks about design, that could be a standalone service, but it makes more sense to me to make it a part of the Question class, as a static method. (If I ever add a new Question type, it’s this method that needs to be updated, so it makes more sense to me to keep them all grouped within the same module.) From the code side, creating the requisite FormControl objects is pretty straight- forward, as follows:
export abstract class Question {
public static toFormGroup(questions: Question[]): FormGroup { let group: any = {};
Notice that the QuestionComponent takes as input the FormGroup to which it (logically) belongs; I could try to find a different means by which to obtain the FormControl (for the isValid property imple- mentation), but this way works and helps keep things simple.
The template for this component is where the real magic of the dynamic form creation takes place. Thanks to a judicious ngSwitch on the Question object’s controlType, I can build the HTML ele- ment pretty simply, as shown in Figure 4.
As you can see, it’s pretty elegant as these things go. I switch on the controlType property, and depending on whether this is a dropdown or a textbox type question, build different HTML.
Last, I just need a QuestionService that serves up some ques- tions, which, again, would usually do so from some external
Figure 2 The QuestionnaireComponent
}
questions.forEach(question => { group[question.key] =
question.required ? new FormControl(question.value, Validators.required) : new FormControl(question.value);
});
return new FormGroup(group);
// ... }
This method basically takes an array of Questions and turns them into an array of FormControl objects nestled inside of a FormGroup object. From this side of things, notice that the only real question is whether the control is required; any other display logic will need to be captured inside the template.
Dynamic Display
I also need to start thinking about the Angular UI components involved here; fundamentally, a poll or questionnaire is made up of one or more questions, so I’ll use that as the working model: a Ques- tionnaireComponent uses some number of QuestionComponents, and each QuestionComponent will have as input a Question object.
It feels a little simpler to start from the top and work my way down, so let’s do that. First off, I have the AppComponent that will display the questionnaire, in this case on its own, as shown in Figure 1.
This code offers up the perfect component scenario. I just use it, and have a service that knows how to provide the input the com- ponent needs, so the code stays light, simple and easily intuitive to any Angular developer.
Next, let’s look at the QuestionnaireComponent, as shown in Figure 2.
Again, the approach is pretty straightforward and simple. The QuestionnaireComponent takes an array of Questions as its input, and uses the FormGroup to match up to the form to be built in the template. Figure 3 shows this.
Generally speaking, the payload would be uploaded via HTTP through an Angular service, presumably to be stored in a database, but that’s taking the example a little out of scope. Here, displaying serves to demonstrate that the data is validated, captured and prepped for distribution.
Of course, I still have to build the individual question elements within the form, and that falls to the QuestionComponent code, shown right here:
@Component({
selector: 'app-question',
templateUrl: './question.component.html'
})
export class QuestionComponent {
@Input() question: Question;
@Input() form: FormGroup;
get isValid() { return this.form.controls[this.question.key].valid; }
}
Figure 3 Preparing to Build the Form with FormGroup
@Component({
selector: 'app-questionnaire',
templateUrl: './questionnaire.component.html'
})
export class QuestionnaireComponent implements OnInit {
@Input() questions: Question[] = []; form: FormGroup;
payload = '';
ngOnInit() {
this.form = Question.toFormGroup(this.questions);
}
onSubmit() {
this.payload = JSON.stringify(this.form.value);
} }
<div>
<form (ngSubmit)="onSubmit()" [formGroup]="form">
<div *ngFor="let question of questions" class="form-row"> <app-question [question]="question" [form]="form"></app-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>
Figure 4 Building the HTML Element
<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 *ngSwitchCase="'dropdown'" [formControlName]="question.key" [id]="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>
msdnmagazine.com
July 2018 13


































































































   17   18   19   20   21