I have a problem with the form in which the arrays are nested. In the console I saw this error:
ERROR TypeError: Cannot read properties of undefined (reading '0')
This is related to FormBuild and FormArray with FormArray inside
Code snippet:
export class TestPreviewDialogComponent implements OnInit {
testModule: TestModule;
testModuleForm: FormGroup;
levels: LanguageLevel[] = [];
constructor(
@Inject(MAT_DIALOG_DATA) public test: TestModule,
public dialogRef: MatDialogRef<TestPreviewDialogComponent>,
private formBuilder: FormBuilder,
private commonService: CommonService
) {
this.testModule = test;
this.testModuleForm = this.formBuilder.group({
moduleName: '',
moduleLevelCode: '',
moduleDescription: '',
questions: this.formBuilder.array([]),
});
}
ngOnInit(): void {
this.getLevels();
this.setTestModuleForm();
setTimeout(() => {
this.testModuleForm.disable();
}, 1000);
}
// Questions
private get questions(): FormArray {
return this.testModuleForm.get('questions') as FormArray;
}
// PRIVATE
private getLevels() {
this.commonService.getLanguageLevels().subscribe({
next: (value) => {
this.levels = value;
},
error: (err) => {
console.log(err);
}
});
}
private setTestModuleForm() {
this.testModuleForm.controls['moduleName'].setValue(this.testModule.name);
this.testModuleForm.controls['moduleLevelCode'].setValue(this.testModule.level);
this.testModuleForm.controls['moduleDescription'].setValue(this.testModule.description);
this.testModuleForm.controls['questions'].setValue(this.setQuestions()); // Console points this place in code
}
setQuestions() {
this.testModule.questions.forEach((x: Question) => {
this.questions.push(
this.formBuilder.group({
questionText: x.questionText,
questionExplanation: x.explanation,
answers: this.setAnswers(x)
})
);
});
}
setAnswers(x: Question) {
let arr = new FormArray([]);
x.answers.forEach((y: Answer) => {
arr.push(
this.formBuilder.group({
answerText: y.text,
isCorrect: y.isCorrect
})
);
});
return arr;
}
}
Full error with stack:
ERROR TypeError: Cannot read properties of undefined (reading '0')
at forms.mjs:1671:18
at forms.mjs:7018:13
at Array.forEach ()
at FormArray._forEachChild (forms.mjs:7017:23)
at assertAllValuesPresent (forms.mjs:1670:13)
at FormArray.setValue (forms.mjs:6840:9)
at TestPreviewDialogComponent.setTestModuleForm (test-preview-dialog.component.ts:66:51)
at TestPreviewDialogComponent.ngOnInit (test-preview-dialog.component.ts:38:14)
at callHook (core.mjs:2498:22)
at callHooks (core.mjs:2467:17)
I feel that the questions are not initialized when setQuestions()
function is executed
and that's why it says reading '0'
. But I can't overcome it as I haven't much experience
EDIT:
<div class="container-box">
<div class="d-flex justify-content-center mat-elevation-z8"
style="border-radius: 10px; margin-bottom: 20px; height: 60px; background-color: #3f51b5;">
<h1 style="color: #ffffff; text-shadow: 1px 1px black; line-height: 60px;">
{{testModule.name}} - {{testModule.level}}
</h1>
</div>
<form class="row px-3 py-3 mx-auto mat-elevation-z8" style="border-radius: 10px" [formGroup]="testModuleForm">
<div class="col-6">
<div>
<mat-label>Nazwa testu</mat-label>
<mat-form-field class="w-100" appearance="fill">
<input formControlName="moduleName" matInput>
</mat-form-field>
</div>
<div>
<mat-label>Poziom testu</mat-label>
<mat-form-field class="w-100" appearance="fill">
<mat-select formControlName="moduleLevelCode" placeholder="Poziom zaawansowania">
<mat-option *ngFor="let lvl of levels" [value]="lvl.shortName">
{{lvl.shortName}} - {{lvl.fullName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="col-6">
<div>
<mat-label>Opis</mat-label>
<mat-form-field class="w-100" appearance="fill">
<textarea formControlName="moduleDescription" matInput style="height: 107px; resize: none;"
>
</textarea>
</mat-form-field>
</div>
</div>
<div class="mt-3">
<div class="d-flex justify-content-center">
<h3 style="font-size: 20px; font-weight:bold;">List pytań</h3>
</div>
</div>
<div class="row col-12 px-3 py-2 mx-auto" formArrayName="questions">
<div class="question-box px-3 py-2" *ngFor="let question of testModule.questions; let questionIndex=index">
<div [formGroupName]="questionIndex">
<div class="row">
<div class="col-1 question-number-box">
<span class="question-number">{{questionIndex + 1}}.</span>
</div>
<div class="col-6">
<mat-label>Pytanie</mat-label>
<mat-form-field class="w-100" appearance="fill">
<input formControlName="questionText" matInput>
</mat-form-field>
</div>
<div class="col-4">
<mat-label>Wyjaśnienie</mat-label>
<mat-form-field class="w-100" appearance="fill">
<input formControlName="questionExplanation" matInput>
</mat-form-field>
</div>
<div class="col-1 remove-question-button-box"></div>
</div>
<div class="row mt-3">
<div class="col-10 d-flex justify-content-center">
<h3 style="font-size: 16px; font-weight:bold;">Odpowiedzi</h3>
</div>
<div class="col-2 d-flex justify-content-center"></div>
</div>
<div class="answers-box row col-12 py-2" formArrayName="answers">
<div *ngFor="let answer of testModule.questions[questionIndex].answers; let answerIndex=index">
<div [formGroupName]="answerIndex">
<div class="row col-12">
<div class="col-2"></div>
<div class="col-1 answer-number-box">
<span class="answer-number">{{answerIndex + 1}}.</span>
</div>
<div class="col-6">
<mat-label>Odpowiedź</mat-label>
<mat-form-field class="w-100" appearance="fill">
<input formControlName="answerText" matInput>
</mat-form-field>
</div>
<div class="col-2">
<mat-label>Poprawna</mat-label>
<mat-form-field class="w-100" appearance="fill">
<mat-select formControlName="isCorrect" placeholder="Poprawna?">
<mat-option [value]="true">Tak</mat-option>
<mat-option [value]="false">Nie</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="col-1 remove-answer-button-box"></div>
</div>
</div>
</div>
</div>
</div>
<div class="divider"></div>
</div>
</div>
</form>
</div>
Update: the problem is the line
this.testModuleForm.controls['questions']
.setValue(this.setQuestions()); //<--WRONG
Replace by
//should be simply a call to the function:
this.setQuestions() //<--OK
See that not give value to the array "questions", else setQuestions give value to the formGroup
note: Always you mannage a FormArray not loop over a variable, loop over the formArray.controls.
<!---WRONG--->
<div *ngFor="let question of testModule.questions;
let questionIndex=index">
<!--OK--->
<div *ngFor="let question of questions.controls;
let questionIndex=index">
See that when we have a FormArray inside a FormArray we need a function to "reach" the formArray -can not be a getter else a function we pass an "index" (the function getAnswer)
<!--WRONG-->
<div *ngFor="let answer of testModule.questions[questionIndex].answers;
let answerIndex=index">
<!--OK-->
<div *ngFor="let answer of getAnswers(questionIndex).controls;
let answerIndex=index">
getAnswer(index:number)
{
return this.questions.at(index).get('answers') as FormArray
}
NOTE: I don't check your code (and don't know if you has really a FormArray), only I want to give a clue.
NOTE2:You can write in .html
<pre>
{{testModuleForm?.value|json}}
</pre>
To see if you really has the data you want -even you can comment the whole form until the data match do you want-