You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
}

问题分析与修复方案

核心错误点

  1. 顶层表单类型错误:form标签绑定的formGroup必须是FormGroup类型,但原代码中直接将ttp_form初始化为FormArray,导致表单结构不合法。
  2. formArrayName名称不匹配:原代码中formArrayName="ttp_form",但实际ttp_form是顶层表单,不是嵌套在其中的FormArray控件。
  3. 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>

修改说明

  1. 表单结构调整:顶层表单改为FormGroup,内部包含一个名为ttpItems的FormArray,符合Angular表单的嵌套规则。
  2. 封装子表单创建方法:createTtpItem方法统一创建子表单组,避免代码重复。
  3. 添加快捷访问方法:get ttpItems()简化HTML中对FormArray的访问,避免复杂的get调用。
  4. 修正formArrayName:HTML中formArrayName改为ttpItems,与FormGroup中的控件名一致。
  5. 修正遍历逻辑:直接遍历ttpItems.controls,避免原代码中错误的sottotecnica访问路径。

内容的提问来源于stack exchange,提问作者Alessandro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 08:04:54