I opened question about dynamically generate form controllers,after that I have some issues with dynamically template generate and controllers generate.
in this project,mainly I have 4 types of questions in an array.I have to generate these questions dynamically
question types are,
MCQ ( only have to select one answer)
Multiple Select ( User can select multiple answers and at least one required)
Ranking question ( user have to give the correct order of answers. answers should be unque)
- Descriptive ( Users own answer can give)
here is my html code
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-transparent border-success">
<h3>15 questions</h3>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-12">
<form [formGroup]="surveyQuestionForm">
<div *ngFor="let question of questions; let i=index">
<div [ngSwitch]="question.qType">
<div *ngSwitchCase="1">
<div class="form-group">
<label class="control-label"> {{question.qNo}})
{{question.question}}</label>
<div class="ml-3">
<table>
<tr *ngFor="let anwr of question.answers; let a=index">
<td>{{a+1}}. {{anwr}} </td>
<td>
<div class="custom-radio custom-control">
<input type="radio" class="custom-control-input"
id="q{{question.qNo}}_{{a}}"
name="q{{question.qNo}}" value="{{a+1}}"
formControlName="q{{question.qNo}}"
[ngClass]="{'is-invalid':surveyQuestionForm.get('q'+ question.qNo).errors && formSubmitted}" />
<label class="custom-control-label"
for="q{{question.qNo}}_{{a}}"></label>
</div>
</td>
</tr>
<div class="text-danger"
*ngIf="surveyQuestionForm.get('q'+ question.qNo).hasError('required') && formSubmitted">
Answer required</div>
</table>
</div>
</div>
</div>
<div *ngSwitchCase="2">
<div class="form-group">
<label class="control-label"> {{question.qNo}})
{{question.question}}</label>
<div class="ml-3">
<table>
<tr *ngFor="let anwr of question.answers; let b=index">
<td>{{b+1}}. {{anwr}} </td>
<td>
<div class="custom-checkbox custom-control">
<input type="checkbox" class="custom-control-input"
id="q{{question.qNo}}_{{b}}" value="{{b+1}}"
formControlName="q{{question.qNo}}"
[ngClass]="{'is-invalid':surveyQuestionForm.get('q'+ question.qNo).errors && formSubmitted}" />
<label class="custom-control-label"
for="q{{question.qNo}}_{{b}}"></label>
</div>
</td>
</tr>
<div class="text-danger"
*ngIf="surveyQuestionForm.get('q'+ question.qNo).hasError('atLeastOneRequired') && formSubmitted">
At least One Answer required</div>
</table>
</div>
</div>
</div>
<div *ngSwitchCase="3">
<div class="form-group">
<label class="control-label"> {{question.qNo}})
{{question.question}}</label>
<div class="ml-3">
<table>
<tr *ngFor="let anwr of question.answers; let a=index">
<td>{{a+1}}. {{anwr}} </td>
<div class="invalid-feedback"
*ngIf="surveyQuestionForm.get('q'+ question.qNo).touched && surveyQuestionForm.get('q'+ question.qNo).hasError('required')">
Answer required</div>
<div class="invalid-feedback"
*ngIf="surveyQuestionForm.get('q'+ question.qNo).touched && surveyQuestionForm.get('q'+ question.qNo).hasError('max')">
max value</div>
<div class="invalid-feedback"
*ngIf="surveyQuestionForm.get('q'+ question.qNo).touched && surveyQuestionForm.get('q'+ question.qNo).hasError('min')">
min value</div>
<div class="invalid-feedback"
*ngIf="surveyQuestionForm.get('q'+ question.qNo).touched && surveyQuestionForm.get('q'+ question.qNo).hasError('notAllUnique')">
Already inserted value</div>
<td>
<input type="number" style="width:40px;"
id="q{{question.qNo}}_{{a}}"
[ngClass]="{'is-invalid': surveyQuestionForm.get('q'+ question.qNo).errors
&& surveyQuestionForm.get('q'+ question.qNo).touched}"
formControlName="q{{question.qNo}}"
class="text-center" />
</td>
</tr>
</table>
</div>
</div>
</div>
<div *ngSwitchCase="4">
<div class="form-group">
<label class="control-label"> {{question.qNo}})
{{question.question}}</label>
<div class="ml-3">
<table>
<th width="auto"></th>
<th></th>
<tr>
<td><textarea class="form-control" rows="5" id="comment" name="text"
[ngClass]="{'is-invalid':surveyQuestionForm.get('q'+ question.qNo).errors &&
surveyQuestionForm.get('q'+ question.qNo).touched}"
formControlName="q{{question.qNo}}"></textarea></td>
</tr>
<div class="text-danger"
*ngIf="surveyQuestionForm.get('q'+ question.qNo).hasError('required') && formSubmitted">
Answer required</div>
</table>
</div>
</div>
</div>
<div *ngSwitchCaseDefault></div>
</div>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-md-12">
<button (click)="onSubmit()" class="btn btn-primary">Submit</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Here is my typescript code
surveyQuestionForm: FormGroup;
formSubmitted = false;
constructor(private fb: FormBuilder) { }
questions: any = [
{
id: 11,
surveyNo: 5,
qNo: 1,
question: 'What is the country you would like to travel?',
qType: 1,
noAnswrs: 4,
answerType: 1,
answers: ['America', 'Australia', 'India', 'England']
},
{
id: 12,
surveyNo: 5,
qNo: 2,
question: 'What type of credit cards do you have?',
qType: 2,
noAnswrs: 4,
answerType: 1,
answers: ['Visa', 'Mastercard', 'American Express', 'Discover']
},
{
id: 13,
surveyNo: 5,
qNo: 3,
question: 'Please rank the following features in order of importance,where 1 is the most important to you.?',
qType: 3,
noAnswrs: 4,
answerType: 1,
answers: ['Location', 'Confort', 'Service', 'Value for money']
},
{
id: 14,
surveyNo: 5,
qNo: 4,
question: 'What is your idea about our institute?',
qType: 4,
noAnswrs: 0,
answerType: 1,
answers: []
}
];
ngOnInit() {
this.createForms();
}
createForms(): any {
this.surveyQuestionForm = this.fb.group(
this.questions.reduce((group: any, question: { qNo: string; }) => {
return Object.assign(group, { ['q' + question.qNo]: this.buildSubGroup(question) });
}, {})
);
}
private buildSubGroup(question) {
switch (question.qType) {
case 1:
return [Validators.required];
case 2:
return this.fb.group(
question.answers.reduce((subGroup, answer) => {
return Object.assign(subGroup, { [answer]: [false] });
}, {}), { validators: [this.atLeastOneRequired()] }
);
case 3:
return this.fb.group(
question.answers.reduce((subGroup, answer) => {
return Object.assign(subGroup, { [answer]: ['', [Validators.required, Validators.min(1), Validators.max(3)]] });
}, {}), { validators: [this.uniqueNumbersValidator()] }
);
case 4:
return this.fb.group({ answer: ['', [Validators.required]] });
default:
throw new Error('unhandled question type');
}
}
atLeastOneRequired() {
return (ctrl: AbstractControl) => {
const fg = ctrl as FormGroup;
const atLeastOneTrue = Object.values(fg.controls).some(fc => !!fc.value);
return (atLeastOneTrue) ? null : { atLeastOneRequired: true };
};
}
uniqueNumbersValidator() {
return (ctrl: AbstractControl) => {
const fg = ctrl as FormGroup;
let allUnique = true;
const values = [];
Object.values(fg.controls).forEach(fc => {
const val = fc.value;
if (val && allUnique) {
if (values.includes(val) && allUnique) {
allUnique = false;
}
values.push(val);
}
});
return (allUnique) ? null : { notAllUnique: true };
};
}
onSubmit() {
this.formSubmitted = true;
console.log(this.formSubmitted);
}
I can see this error,
control.registerOnChange is not a function
here is the stackblitz link https://stackblitz.com/edit/angular-nya7l9
Please help me to solve this issue.
thanks
You have nested formgroups, so in the template you should add formgroupname to them. I don't know if it intentional or not, you can see it on the console. I've changed you stackblitz.
https://angular-kdhmha.stackblitz.io
first, issue with your radio building and binding:
built it like this:
and bind like this:
use the formGroupName directive up top then just access the answer control statically.
next your checkbox binding:
again, use the formGroupName directive since your overall form is just a group of groups, then within, your formControlNames are the answers themselves.
now your multiple choice binding, generally the same issues:
and
finally, your free text answer, basically the same problems, need the formGroupName directive, and to bind correctly to the static answer control within it:
fixed blitz:
https://stackblitz.com/edit/angular-d4p6ly?file=src/app/app.component.html