Angular响应式表单报错:Cannot find control with name: 0 求助
Angular表单错误排查:Cannot find control with name: '0' 及路径错误
开发Angular测验题添加组件时,点击添加题目后出现以下错误:Cannot find control with name: 0Cannot find control with path: '0 -> questionText'
组件TS代码
@Component({ selector: 'app-add-quiz', templateUrl: './add-quiz.component.html', styleUrls: ['./add-quiz.component.css'] }) export class AddQuizComponent implements OnInit { public AddForm!: FormGroup; public questionForms: FormGroup[] = []; public deps: any = []; minDate: any; maxDate: any; private questionIdCounter = 1; currentAnswerId: number = 1; constructor(private fb: FormBuilder, private router: Router, private auth: AuthService, private api: ApiService) { } ngOnInit(): void { } addQuestion() { this.questionForms.push(this.createQuestionFormGroup()); console.log(this.questionForms); this.questionIdCounter++; } createQuestionFormGroup(): FormGroup { return this.fb.group({ id: this.questionIdCounter, questionText: new FormControl('', Validators.required), // Update this line selectedAnswerIndex: null, answers: this.fb.array([]), }); } addAnswer(questionForm: FormGroup) { const answersArray = questionForm.get('answers') as FormArray; answersArray.push(this.createAnswerFormGroup()); // Set answerText value here } createAnswerFormGroup() { return this.fb.group({ answerText: new FormControl(['', Validators.required]), // Set default value for answerText isCorrect: [false] }); } isFormValid(): boolean { if (this.AddForm.valid) { return true; } else { return false } } async onSubmit() { if (this.AddForm.valid) { const quizData: QuizRequest = { quiz: { quizName: this.AddForm.get('quizName')?.value, quizStatus: this.AddForm.get('quizStatus')?.value, depId: this.AddForm.get('depId')?.value, expiryDate: this.AddForm.get('expiryDate')?.value }, questions: this.questionForms.map((questionForm) => { const answersArray = questionForm.get('answers') as FormArray; return { questionText: questionForm.get('questionText')?.value, correctAnswerIndex: questionForm.get('selectedAnswerIndex')?.value, answers: answersArray.controls.map((answerCtrl) => { return { answerText: answerCtrl.get('answerText')?.value, isCorrect: answerCtrl.get('isCorrect')?.value }; }) }; }) }; console.log('Quiz Data:', quizData); try { console.log('onSubmit - quizData', quizData); await this.api.addQuiz(quizData).toPromise(); this.showSuccessMessage(); this.resetForm(); // Reset the form after successful submission } catch (error: any) { let errorMessage = 'An error occurred while submitting the quiz.'; if (error.status === 400) { errorMessage = 'Bad request: Invalid data provided.'; } else if (error.status === 403) { errorMessage = 'You do not have permission to submit the quiz.'; } else if (error.status === 500) { errorMessage = 'Internal server error. Please try again later.'; } this.showErrorMessage(errorMessage); } } }
HTML代码
<div class="container mt-5"> <form [formGroup]="AddForm" (ngSubmit)="onSubmit()" class="quiz-form"> <div class="row mt-4"> <div class="col-md-12"> <button type="button" class="btn btn-success btn-block" (click)="addQuestion()">Add Question</button> </div> </div> <div> <div class="row question-row" *ngFor="let questionForm of questionForms; let i = index" [formGroupName]="i"> <div class="col-md-12 question-container"> <div class="form-group question-header"> <label class="question-label" for="questionText{{ i }}">{{ 'Question ' + (i + 1) }}</label> <input formControlName="questionText" type="text" class="form-control" [id]="'questionText_' + i"> <button type="button" class="btn btn-danger btn-remove" (click)="removeQuestion(i)">Remove Question</button> </div> <div class="form-group" *ngFor="let answerCtrl of getAnswerControls(questionForm); let j = index"> <label for="answerText{{ i }}_{{ j }}">{{ 'Answer ' + (j + 1) }}</label> <button type="button" class="btn btn-danger " (click)="removeAnswer(questionForm, j)">Remove Answer</button> <input formControlName="answerText" type="text" class="form-control" [id]="'answerText_' + i + '_' + j"> </div> <button type="button" class="btn btn-primary btn-add-answer" (click)="addAnswer(questionForm)">Add Answer</button> </div> </div> </div> <div class="row submit-row"> <div class="col-md-12"> <button type="submit" class="btn btn-primary btn-submit" [disabled]="!isFormValid()">Submit Quiz</button> </div> </div> </form> </div>
完整错误信息
core.mjs:8400 ERROR Error: Cannot find control with name: '0' at _throwError (forms.mjs:3151:11) at setUpFormContainer (forms.mjs:3133:9) at FormGroupDirective._setUpFormContainer (forms.mjs:4914:9) at FormGroupDirective.addFormGroup (forms.mjs:4801:14) at FormGroupName.ngOnInit (forms.mjs:3672:28) at callHook (core.mjs:2434:22) at callHooks (core.mjs:2403:17) at executeInitAndCheckHooks (core.mjs:2354:9) at selectIndexInternal (core.mjs:9059:17) at Module.ɵɵadvance (core.mjs:9042:5) handleError @ core.mjs:8400 (anonymous) @ core.mjs:25320 invoke @ zone.js:375 run @ zone.js:134 runOutsideAngular @ core.mjs:24109 tick @ core.mjs:25320 (anonymous) @ core.mjs:25169 invoke @ zone.js:375 onInvoke @ core.mjs:24210 invoke @ zone.js:374 run @ zone.js:134 run @ core.mjs:24064 next @ core.mjs:25168 next @ Subscriber.js:91 _next @ Subscriber.js:60 next @ Subscriber.js:31 (anonymous) @ Subject.js:34 errorContext @ errorContext.js:19 next @ Subject.js:27 emit @ core.mjs:20900 checkStable @ core.mjs:24132 onLeave @ core.mjs:24260 onInvokeTask @ core.mjs:24204 invokeTask @ zone.js:408 runTask @ zone.js:178 invokeTask @ zone.js:490 invokeTask @ zone.js:1664 globalCallback @ zone.js:1695 globalZoneAwareCallback @ zone.js:1728 Show 29 more frames Show less add-quiz.component.html:70 ERROR Error: Cannot find control with path: '0 -> questionText' at _throwError (forms.mjs:3151:11) at setUpControl (forms.mjs:2934:13) at FormGroupDirective.addControl (forms.mjs:4771:9) at FormControlName._setUpControl (forms.mjs:5321:43) at FormControlName.ngOnChanges (forms.mjs:5266:18) at FormControlName.rememberChangeHistoryAndInvokeOnChangesHook (core.mjs:1521:14) at callHook (core.mjs:2444:18) at callHooks (core.mjs:2403:17) at executeInitAndCheckHooks (core.mjs:2354:9) at selectIndexInternal (core.mjs:9059:17)
问题根源
错误本质是表单控件层级不匹配:
- HTML里用
[formGroupName]="i",是告诉Angular当前元素属于AddForm这个FormGroup下的第i个控件,但AddForm初始化时根本没有包含存储题目的FormArray,完全找不到名为0的控件。 - 同时你把题目存在独立的
questionForms数组里,和AddForm没有关联,导致表单指令无法识别控件路径。
修复步骤
1. 初始化AddForm并添加题目FormArray
在ngOnInit里正确初始化表单,包含测验基础信息和题目数组:
ngOnInit(): void { this.AddForm = this.fb.group({ quizName: ['', Validators.required], quizStatus: ['', Validators.required], depId: ['', Validators.required], expiryDate: ['', Validators.required], questions: this.fb.array([]) // 新增:存储题目的FormArray }); } // 新增:获取题目FormArray的快捷方法 get questionsArray(): FormArray { return this.AddForm.get('questions') as FormArray; }
2. 修改addQuestion方法,往FormArray里添加题目
不再使用独立的questionForms数组,直接操作AddForm内部的FormArray:
addQuestion() { this.questionsArray.push(this.createQuestionFormGroup()); this.questionIdCounter++; }
3. 修正HTML中的题目遍历逻辑
遍历questionsArray.controls,并通过formArrayName和formGroupName正确关联表单路径:
<div formArrayName="questions"> <div class="row question-row" *ngFor="let questionCtrl of questionsArray.controls; let i = index" [formGroupName]="i"> <div class="col-md-12 question-container"> <div class="form-group question-header"> <label class="question-label" for="questionText{{ i }}">{{ 'Question ' + (i + 1) }}</label> <input formControlName="questionText" type="text" class="form-control" [id]="'questionText_' + i"> <button type="button" class="btn btn-danger btn-remove" (click)="removeQuestion(i)">Remove Question</button> </div> <!-- 答案部分需要嵌套formArrayName --> <div formArrayName="answers"> <div class="form-group" *ngFor="let answerCtrl of getAnswerControls(questionCtrl); let j = index" [formGroupName]="j"> <label for="answerText{{ i }}_{{ j }}">{{ 'Answer ' + (j + 1) }}</label> <button type="button" class="btn btn-danger " (click)="removeAnswer(i, j)">Remove Answer</button> <input formControlName="answerText" type="text" class="form-control" [id]="'answerText_' + i + '_' + j"> <!-- 可选:添加正确答案选择框 --> <input type="checkbox" formControlName="isCorrect" id="isCorrect_{{i}}_{{j}}"> <label for="isCorrect_{{i}}_{{j}}">设为正确答案</label> </div> </div> <button type="button" class="btn btn-primary btn-add-answer" (click)="addAnswer(i)">Add Answer</button> </div> </div> </div>
4. 更新答案相关方法,通过索引操作FormArray
原来的addAnswer和removeAnswer需要改为通过题目索引获取对应FormGroup:
addAnswer(questionIndex: number) { const questionGroup = this.questionsArray.at(questionIndex) as FormGroup; const answersArray = questionGroup.get('answers') as FormArray; answersArray.push(this.createAnswerFormGroup()); } removeAnswer(questionIndex: number, answerIndex: number) { const questionGroup = this.questionsArray.at(questionIndex) as FormGroup; const answersArray = questionGroup.get('answers') as FormArray; answersArray.removeAt(answerIndex); } // 更新getAnswerControls方法,接收FormControl而非FormGroup(兼容遍历的questionCtrl) getAnswerControls(questionCtrl: AbstractControl): AbstractControl[] { return (questionCtrl as FormGroup).get('answers')?.controls || []; } // 新增删除题目方法 removeQuestion(questionIndex: number) { this.questionsArray.removeAt(questionIndex); }
5. 修正onSubmit和isFormValid逻辑
现在题目数据在AddForm内部,不需要再依赖questionForms数组:
isFormValid(): boolean { return this.AddForm.valid; } async onSubmit() { if (this.AddForm.valid) { const formValue = this.AddForm.value; const quizData: QuizRequest = { quiz: { quizName: formValue.quizName, quizStatus: formValue.quizStatus, depId: formValue.depId, expiryDate: formValue.expiryDate }, questions: formValue.questions.map((question: any, index: number) => ({ questionText: question.questionText, correctAnswerIndex: question.answers.findIndex((ans: any) => ans.isCorrect), // 通过isCorrect字段获取正确答案索引 answers: question.answers.map((answer: any) => ({ answerText: answer.answerText, isCorrect: answer.isCorrect })) })) }; console.log('Quiz Data:', quizData); try { await this.api.addQuiz(quizData).toPromise(); this.showSuccessMessage(); this.AddForm.reset(); // 重置整个表单 // 重置计数器 this.questionIdCounter = 1; } catch (error: any) { let errorMessage = '提交测验时发生错误。'; if (error.status === 400) { errorMessage = '请求错误:提供的数据无效。'; } else if (error.status === 403) { errorMessage = '你没有提交测验的权限。'; } else if (error.status === 500) { errorMessage = '服务器内部错误,请稍后重试。'; } this.showErrorMessage(errorMessage); } } }
6. 修正createAnswerFormGroup的小问题
原来的answerText用了数组形式的初始值,改为普通字符串:
createAnswerFormGroup() { return this.fb.group({ answerText: ['', Validators.required], // 去掉外层数组 isCorrect: [false] }); }
内容的提问来源于stack exchange,提问作者laithadnan
相关产品推荐
相关产品推荐

