I have a deep nested form that when filled out, a request is sent to an REST endpoint. The first two levels of my nested form are working fine, it's when I get to the third level is when I'm having all kinds of issues. One issue was that I was only able to type on character at a time inside of the text but this thread is in regards to this error, ERROR TypeError: Cannot read properties of null (reading 'controls'). I've tried for good amount of time now but without a good result. Looking for another set of eyes to look at it.
Here is the response that I am trying to send:
{
"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": []
}
}
}
]
}
]
}
}
Everything is working until I get to "recipients". This is where everything breaks. Here is my typescript file:
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()]),
}),
});
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([
// this.envelopeCarbonCopies()
]),
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)
})
}
and here is the 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 class="alert alert-danger" *ngIf="signers().controls[i].get('name').hasError('required') && signers().controls[i].get('name').touched">Name is required</div> -->
</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>
</div>
any help would be greatly appreciated.
The problem was you are incorrectly accessing the signers
form array. From your response, your Reactive form structure should be as:
compositeTemplates (FormArray)
inlineTemplates (FormArray)
recipients (FormGroup)
signers (FormArray)
To access it correctly,
signers(comIndex: number, skillIndex: number): FormArray {
return this.compositeTemplates()
.at(comIndex)
.get(`inlineTemplates.${skillIndex}.recipients.signers`) as FormArray;
}
<ng-container
*ngFor="let s of signers(comIndex, skillIndex).controls; let i = index"
[formGroupName]="i"
>
...
</ng-container>
Since the signers
method is changed by adding a new parameter, this will affect to addSigner
and removeSigner
methods as well.
addSigner(k: number, s: number) {
this.signers(k, s).push(this.envelopeSigners());
}
removeSigner(k: number, s: number, i: number) {
this.signers(k, s).removeAt(i);
}
<fa-icon
[icon]="faPlus"
(click)="addSigner(comIndex, skillIndex)"
></fa-icon>
<fa-icon
[icon]="faMinus"
*ngIf="i"
(click)="removeSigner(comIndex, skillIndex, i)"
></fa-icon>