In my Angular component, I have implemented a feature where a form is dynamically created and rendered using ReactiveFormsModule. Upon clicking the submit button, the data is posted and new questions are fetched to replace the old ones in the component.
The initial rendering works well when you first visit the page. However, after submitting the first question, the parent component sends new questions to be displayed in this component.
An error occurs stating: "
There is no FormControl instance attached to form control element with name: 'q1124'
". However, logging form.controls confirms that it does contain 'q1124'.
Here is the content of form.component.ts:
import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { Question } from '../../../../shared/models/views-models/question-view';
import { getQuestionAnswers } from '../../../../shared/utils/helpers.util';
import { MatButtonModule } from '@angular/material/button';
import { MatCard } from '@angular/material/card';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
@Component({
selector: 'app-survey-form',
standalone: true,
imports: [FormsModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatCard, MatButtonModule,],
templateUrl: './survey-form.component.html',
styleUrl: './survey-form.component.scss'
})
export class SurveyFormComponent implements OnChanges {
form: FormGroup = new FormGroup({});
@Input() questions: Question[] = []
@Input() currentState: string | undefined;
@Input() previousState: string | undefined;
@Output() dataEmitter: EventEmitter<any[]> = new EventEmitter<any[]>();
constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef) { }
ngOnChanges(changes: SimpleChanges): void {
if (changes['questions'] && changes['questions'].currentValue !== changes['questions'].previousValue) {
this.form = this.createForm(this.fb, this.questions)
this.cdr.detectChanges();
}
}
submit() {
if (this.form.valid) {
const answers = getQuestionAnswers(this.form, this.questions)
this.dataEmitter.emit(answers)
}
}
createForm = (
formBuilder: FormBuilder,
questions: any[]
): FormGroup => {
const group: { [key: string]: AbstractControl<any, any> } = {};
questions.forEach(question => {
group[question.id] = question.mandatory
? formBuilder.control('', [Validators.required])
: formBuilder.control('');
});
return new FormGroup(group)
}
}
This is the content of form.component.html:
<div class="form-container">
<form [formGroup]="form" (ngSubmit)="submit()" class="input-form">
@for (question of questions; track $index) {
<input type="text" matInput [formControlName]="question.id" [required]="question.mandatory"
placeholder="Type your answer here..." [id]="question.id">
}
@if (currentState) {
<button type="submit" [disabled]="form.invalid" mat-flat-button>Next</button>
}
</form>
</div>
I have tried using different lifecycle hooks like ngOnAfterInit to address timing issues, and also included the ChangeDetectorRef for debugging purposes.
For further reference, here is a link to a Stackblitz mockup: https://stackblitz.com/edit/stackblitz-starters-4ewzry?file=src%2Fapp%2Fform-test%2Fform-test.component.ts