Angular深层嵌套响应式表单报错:TypeError: Cannot read properties of null (reading 'controls')
Angular深层嵌套响应式表单报错:Cannot read properties of null (reading 'controls')
问题概述
开发了一个深层嵌套的Angular响应式表单,提交时调用REST接口。表单前两层运行正常,但第三层(recipients相关部分)出现问题:曾遇到文本框一次只能输入一个字符的情况,当前报错:
ERROR TypeError: Cannot read properties of null (reading 'controls')
请求结构
{ "appCode": "", "accountType": "", "emailId": "", "IDV": "", "emailSubject": null, "emailBlurb": null, "envelopeRequest": { "compositeTemplates": [ { "compositeTemplateId": 13, "serverTemplates": [ { "sequence": 93, "templateId": "" } ], "inlineTemplates": [ { "sequence": 10, "recipients": { "signers": [ { "name": null, "email": "", "recipientId": 41 } ], "carbonCopies": [], "customFields": { "listCustomFields": [], "textCustomFields": [] } } } ] } ] } }
现有代码
TypeScript代码
import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms'; export class YourComponent { templateForm = this.fb.group({ appCode: ['esg'], accountType: [''], emailId: [''], IDV: [''], emailSubject: [null, Validators.required], emailBlurb: [null, Validators.required], envelopeRequest: this.fb.group({ compositeTemplates: this.fb.array([this.newCompositeTemplate()]), }), }); constructor(private fb: FormBuilder) {} randomNumber(max: number) { return Math.floor(Math.random() * max) + 1; } compositeTemplates(): FormArray { return this.templateForm.get('envelopeRequest.compositeTemplates') as FormArray; } newCompositeTemplate(): FormGroup { return this.fb.group({ compositeTemplateId: this.randomNumber(100), serverTemplates: this.fb.array([this.newTemplate()]), inlineTemplates: this.fb.array([this.newSequence()]) }); } addCompositeTemplate() { this.compositeTemplates().push(this.newCompositeTemplate()); } removeCompositeTemplate(comIndex: number) { this.compositeTemplates().removeAt(comIndex); } serverTemplates(comIndex: number): FormArray { return this.compositeTemplates().at(comIndex).get('serverTemplates') as FormArray; } inlineTemplates(comIndex: number): FormArray { return this.compositeTemplates().at(comIndex).get('inlineTemplates') as FormArray; } signers(comIndex: number): FormArray { return this.compositeTemplates().at(comIndex).get('recipients.signers') as FormArray; } addSigner(k: number){ this.signers(k).push(this.envelopeSigners()); } removeSigner(k: number, i: number) { this.signers(k).removeAt(i); } newTemplate(): FormGroup { return this.fb.group({ sequence: this.randomNumber(100), templateId: '', }); } newSequence(): FormGroup { return this.fb.group({ sequence: this.randomNumber(10), recipients: this.fb.group({ signers: this.fb.array([this.envelopeSigners()]), carbonCopies: this.fb.array([]), customFields: this.fb.group({ listCustomFields: this.fb.array([]), textCustomFields: this.fb.array([]) }) }) }) } addServerTemplate(serverIndex: number) { this.serverTemplates(serverIndex).push(this.newTemplate()); } removeServerTemplate(serverIndex: number, skillIndex: number) { this.serverTemplates(serverIndex).removeAt(skillIndex); } envelopeSigners(): FormGroup { return this.fb.group({ name: [null, Validators.required], email: ['',[Validators.required, Validators.pattern("^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$")] ], recipientId: this.randomNumber(50) }) } }
HTML代码
<div formArrayName="compositeTemplates"> <div *ngFor="let c of compositeTemplates().controls; let comIndex=index"> <div [formGroupName]="comIndex"> <input type="hidden" formControlName="compositeTemplateId" /> <div formArrayName="serverTemplates"> <div *ngFor="let skill of serverTemplates(comIndex).controls; let skillIndex=index"> <div [formGroupName]="skillIndex"> <input type="hidden" formControlName="sequence" /> <input type="hidden" formControlName="templateId" /> </div> </div> </div> <div formArrayName="inlineTemplates"> <div *ngFor="let skill of inlineTemplates(comIndex).controls; let skillIndex=index"> <div [formGroupName]="skillIndex"> <input type="hidden" formControlName="sequence" /> <ng-container formGroupName="recipients"> <div style="margin-top: 0.3vh; position: absolute; right: 3vw;"> <fa-icon [icon]="faPlus" (click)='addSigner(i)'></fa-icon> </div> <ng-container formArrayName="signers"> <ng-container *ngFor="let s of signers(comIndex).controls let i = index" [formGroupName] = "i"> <div style="position: absolute;right: 3vw;margin-top: 3vh;color: red;"><fa-icon [icon]="faMinus" *ngIf="i" (click)="removeSigner(i)"></fa-icon></div> <div class="row mt-4"> <div class="col-auto"> <label class="col-form-label">Full Name:</label> </div> <div class="col-md-5"> <input type="text" formControlName="name" id="{{'name'+i}}" placeholder="Enter name" class="form-control"> </div> <div class="col-auto"> <label class="col-form-label">To:</label> </div> <div class="col-md-5"> <input type="text" formControlName="email" id="{{'email'+i}}" placeholder="Enter email address" class="form-control"> </div> </div> </ng-container> </ng-container> </ng-container> </div> </div> </div> </div> </div> </div>
错误分析与修复方案
1. 核心错误:signers方法路径错误
当前signers方法直接从compositeTemplates的对象中获取recipients.signers,但recipients实际存在于inlineTemplates数组的表单组中,导致get('recipients.signers')返回null,读取controls时报错。
修复TS中的signers、addSigner、removeSigner方法:
// 新增inlineIndex参数,支持定位到具体的inlineTemplate signers(comIndex: number, inlineIndex: number = 0): FormArray { return this.inlineTemplates(comIndex).at(inlineIndex).get('recipients.signers') as FormArray; } addSigner(comIndex: number, inlineIndex: number = 0){ this.signers(comIndex, inlineIndex).push(this.envelopeSigners()); } removeSigner(comIndex: number, inlineIndex: number = 0, signerIndex: number) { this.signers(comIndex, inlineIndex).removeAt(signerIndex); }
2. 模板中方法调用参数错误
模板中addSigner(i)和removeSigner(i)的参数不正确,i的作用域错误,且缺少必要的索引参数。
修复HTML代码:
<div formArrayName="compositeTemplates"> <div *ngFor="let c of compositeTemplates().controls; let comIndex=index"> <div [formGroupName]="comIndex"> <input type="hidden" formControlName="compositeTemplateId" /> <div formArrayName="serverTemplates"> <div *ngFor="let skill of serverTemplates(comIndex).controls; let skillIndex=index"> <div [formGroupName]="skillIndex"> <input type="hidden" formControlName="sequence" /> <input type="hidden" formControlName="templateId" /> </div> </div> </div> <div formArrayName="inlineTemplates"> <div *ngFor="let skill of inlineTemplates(comIndex).controls; let skillIndex=index"> <div [formGroupName]="skillIndex"> <input type="hidden" formControlName="sequence" /> <ng-container formGroupName="recipients"> <div style="margin-top: 0.3vh; position: absolute; right: 3vw;"> <!-- 传入正确的comIndex和skillIndex --> <fa-icon [icon]="faPlus" (click)='addSigner(comIndex, skillIndex)'></fa-icon> </div> <ng-container formArrayName="signers"> <!-- 调用修复后的signers方法,添加trackBy优化变更检测 --> <ng-container *ngFor="let s of signers(comIndex, skillIndex).controls; let i = index; trackBy: trackByIndex" [formGroupName]="i"> <div style="position: absolute;right: 3vw;margin-top: 3vh;color: red;"> <!-- 传入完整的索引参数 --> <fa-icon [icon]="faMinus" *ngIf="i" (click)="removeSigner(comIndex, skillIndex, i)"></fa-icon> </div> <div class="row mt-4"> <div class="col-auto"> <label class="col-form-label">Full Name:</label> </div> <div class="col-md-5"> <input type="text" formControlName="name" id="{{'name'+i}}" placeholder="Enter name" class="form-control"> </div> <div class="col-auto"> <label class="col-form-label">To:</label> </div> <div class="col-md-5"> <input type="text" formControlName="email" id="{{'email'+i}}" placeholder="Enter email address" class="form-control"> </div> </div> </ng-container> </ng-container> </ng-container> </div> </div> </div> </div> </div> </div>
3. 解决文本框只能输入一个字符的问题
模板中频繁调用signers(comIndex, skillIndex).controls会导致每次变更检测都生成新的数组引用,重置表单状态。添加trackBy函数优化变更检测:
在组件中添加trackBy函数:
trackByIndex(index: number): number { return index; }
总结
- 修正表单控件的路径定位,确保正确获取到
recipients.signers表单数组 - 同步修正模板中方法调用的参数,传递正确的索引值
- 使用
trackBy函数优化变更检测,解决输入框状态重置问题
内容的提问来源于stack exchange,提问作者fearless924
相关产品推荐
相关产品推荐

