Angular动态表单报错:formArrayName及controls读取问题求助
解决Angular动态表单formArrayName报错问题
问题说明
实现支持字段增删的Angular动态表单时,HTML中formArrayName出现报错,控制台提示:
- 无法找到名为'ttp_form'的控件
- TypeError:无法读取null的'controls'属性
控制台报错信息
in.ts:5 ERROR Error: Cannot find control with name: 'ttp_form' at _throwError (forms.mjs:3151:11) at setUpFormContainer (forms.mjs:3133:9) at FormGroupDirective._setUpFormContainer (forms.mjs:4914:9) at FormGroupDirective.addFormArray (forms.mjs:4827:14) at FormArrayName.ngOnInit (forms.mjs:5123:28) at callHook (core.mjs:2450:22) at callHooks (core.mjs:2419:17) at executeInitAndCheckHooks (core.mjs:2370:9) at selectIndexInternal (core.mjs:9075:17) at Module.ɵɵadvance (core.mjs:9058:5) main.ts:5 ERROR TypeError: Cannot read properties of null (reading 'controls') at HeroFormComponent_Template (hero-form.component.html:6:34) at executeTemplate (core.mjs:10457:9) at refreshView (core.mjs:10342:13) at refreshComponent (core.mjs:11401:13) at refreshChildComponents (core.mjs:10132:9) at refreshView (core.mjs:10392:13) at refreshComponent (core.mjs:11401:13) at refreshChildComponents (core.mjs:10132:9) at refreshView (core.mjs:10392:13) at detectChangesInternal (core.mjs:11545:9)
原组件代码
HTML代码
<form [formGroup]="ttp_form" (ngSubmit)="onSubmit()"> <div formArrayName="ttp_form"> <div *ngFor="let child of ttp_form.get('sottotecnica')['controls']; index as i" formGroupName="{{i}}"> <!--Tattica--> <div class="form-group col-md-4"> <label for="tattica">Tattica</label> <select class="form-control" name="tattica" formControlName="tattica"> <option *ngFor="let tat of tattiche" [value]="tat">{{tat}}</option> </select> </div> <!--Tecnica--> <div class="form-group col-md-4"> <label for="tecnica">Tecnica</label> <select class="form-control" name="tecnica" formControlName="tecnica"> <option *ngFor="let tec of tecniche" [value]="tec">{{tec}}</option> </select> </div> <!--Sotto-Tecnica--> <div class="form-group col-md-4"> <label for="sottotecnica">Sotto-Tecnica</label> <select class="form-control" name="sottotecnica" formControlName="sottotecnica"> <option *ngFor="let sot of sottotecniche" [value]="sot">{{sot}}</option> </select> </div> <a class="btn btn-sm btn-success" (click)="addField()">Aggiungi TTP</a> <br> <a class="btn btn-sm btn-danger" (click)="removeField(i)">Rimuovi TTP</a> <button type="submit" class="btn btn-success" [disabled]="!ttp_form.valid">Invia</button> </div> </div> </form>
TS代码
import { Component, OnInit } from '@angular/core'; import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms'; @Component({ selector: 'app-hero-form', templateUrl: './hero-form.component.html', styleUrls: ['./hero-form.component.css'] }) export class HeroFormComponent implements OnInit { ttp_form: FormGroup | any constructor(){ } ngOnInit(): void { this.ttp_form = new FormArray([ new FormGroup({ tattica: new FormControl(null, Validators.required), tecnica: new FormControl(null, Validators.required), sottotecnica: new FormControl(null) }) ]) } addField() { const control = <FormArray>this.ttp_form.controls["sottotecnica"] control.push( new FormGroup({ tattica: new FormControl(null, Validators.required), tecnica: new FormControl(null, Validators.required), sottotecnica: new FormControl(null) }) ); } removeField(index){ const control = <FormArray>this.ttp_form.controls['sottotecnica']; control.removeAt(index); }
问题分析与修复方案
核心错误点
- 顶层表单类型错误:
form标签绑定的formGroup必须是FormGroup类型,但原代码中直接将ttp_form初始化为FormArray,导致表单结构不合法。 - formArrayName名称不匹配:原代码中
formArrayName="ttp_form",但实际ttp_form是顶层表单,不是嵌套在其中的FormArray控件。 - FormArray访问错误:
addField和removeField方法中错误地尝试访问sottotecnica作为FormArray的名称,但sottotecnica是每个子表单组中的控件,不是FormArray的标识。
修复后的代码
TS代码修改
import { Component, OnInit } from '@angular/core'; import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms'; @Component({ selector: 'app-hero-form', templateUrl: './hero-form.component.html', styleUrls: ['./hero-form.component.css'] }) export class HeroFormComponent implements OnInit { ttp_form: FormGroup; constructor(){ } ngOnInit(): void { // 顶层表单是FormGroup,内部包含一个名为ttpItems的FormArray this.ttp_form = new FormGroup({ ttpItems: new FormArray([ this.createTtpItem() ]) }) } // 封装创建子表单组的方法,复用代码 createTtpItem(): FormGroup { return new FormGroup({ tattica: new FormControl(null, Validators.required), tecnica: new FormControl(null, Validators.required), sottotecnica: new FormControl(null) }) } // 获取FormArray的快捷方法,方便HTML中调用 get ttpItems(): FormArray { return this.ttp_form.get('ttpItems') as FormArray; } addField() { this.ttpItems.push(this.createTtpItem()); } removeField(index: number){ this.ttpItems.removeAt(index); } onSubmit() { // 处理表单提交逻辑 console.log(this.ttp_form.value); } }
HTML代码修改
<form [formGroup]="ttp_form" (ngSubmit)="onSubmit()"> <!-- formArrayName对应FormGroup中的FormArray控件名"ttpItems" --> <div formArrayName="ttpItems"> <!-- 遍历ttpItems这个FormArray,用get方法获取控件 --> <div *ngFor="let child of ttpItems.controls; index as i" formGroupName="{{i}}"> <!--Tattica--> <div class="form-group col-md-4"> <label for="tattica">Tattica</label> <select class="form-control" formControlName="tattica"> <option *ngFor="let tat of tattiche" [value]="tat">{{tat}}</option> </select> </div> <!--Tecnica--> <div class="form-group col-md-4"> <label for="tecnica">Tecnica</label> <select class="form-control" formControlName="tecnica"> <option *ngFor="let tec of tecniche" [value]="tec">{{tec}}</option> </select> </div> <!--Sotto-Tecnica--> <div class="form-group col-md-4"> <label for="sottotecnica">Sotto-Tecnica</label> <select class="form-control" formControlName="sottotecnica"> <option *ngFor="let sot of sottotecniche" [value]="sot">{{sot}}</option> </select> </div> <a class="btn btn-sm btn-success" (click)="addField()">Aggiungi TTP</a> <br> <a class="btn btn-sm btn-danger" (click)="removeField(i)">Rimuovi TTP</a> </div> </div> <button type="submit" class="btn btn-success" [disabled]="!ttp_form.valid">Invia</button> </form>
修改说明
- 表单结构调整:顶层表单改为
FormGroup,内部包含一个名为ttpItems的FormArray,符合Angular表单的嵌套规则。 - 封装子表单创建方法:
createTtpItem方法统一创建子表单组,避免代码重复。 - 添加快捷访问方法:
get ttpItems()简化HTML中对FormArray的访问,避免复杂的get调用。 - 修正formArrayName:HTML中
formArrayName改为ttpItems,与FormGroup中的控件名一致。 - 修正遍历逻辑:直接遍历
ttpItems.controls,避免原代码中错误的sottotecnica访问路径。
内容的提问来源于stack exchange,提问作者Alessandro
相关产品推荐
相关产品推荐

