Search code examples
angulartypescriptangular-reactive-forms

ERROR TypeError: Cannot read properties of null (reading 'controls') in my deep nested reactive form


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.


Solution

  • 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>
    

    Demo @ StackBlitz