Home > Back-end >  How can I use FormArray with FormGroups with Angular reactive forms?
How can I use FormArray with FormGroups with Angular reactive forms?

Time:09-29

I am trying to mimic my model in my reactive form. I've set up a few FormGroups to "nest" things that make sense according to my model. I'm having trouble setting up my component to read the values--which suggests that I likely don't have my template set up correctly either.

Depending if I am editing an existing location or creating a new one,

@Input() location: ILocation;

could be undefined. Currently I am only focused on working with an existing location, so I know location has a value.

// location.model.ts

name: string;
...
messaging: [
    {
        email: {
            fromName: string;
            fromAddress: string;
        };
    }
];
...
createdAt?: string;
updatedAt?: string;
deletedAt?: string;

In my template I am using ngClass to give the user validation feedback:

// location.commponent.html

<div formGroupName="messaging">
    <div formGroupName="email">
        ...
        <div [ngClass]="form.get(['messaging', 'email', 'fromName'])!.errors && (form.get(['messaging', 'email', 'fromName'])!.dirty || form.get(['messaging', 'email', 'fromName'])!.touched) ? 'red' : 'green'">
            <input name="fromName"/>
        </div>
        <!-- fromAddress -->
    </div>
</div>

In my component, I am passing the model in with input binding, then setting up my form goup(s) and form fields like this:

// location.component.ts

@Input() location: ILocation; 

form: FormGroup;

...

ngOnInit(): void {
    this.form = new FormGroup({name: new FormControl(this.location.name, [Validators.required]),
    messaging: new FormGroup({
    email: new FormGroup({
        fromName: new FormControl(this.location.messaging[0].email.fromName, [Validators.required]),
        fromAddress: new FormControl(this.location.messaging[0].email.fromAddress, [Validators.required]),
        }),
    }),
}

The error I am seeing is:

Cannot read properties of undefined (reading 'email')

If I log out what is in the component:

console.log('messaging: ', this.location.messaging);

// email: {fromName: 'No Reply <[email protected]>', fromAddress: '[email protected]'}

I've tried various methods of messaging['email'] or messaging.email messaging[0] but I can't seem to find the correct path.

I am also not sure if I am using the get() method correctly in my template.

How can I set up my form to read/present the data correctly?

Update:

Not surprisingly, a big problem I was having was sending the wrong shape of data back.

In the end, this is the JSON I am trying to create:

[{"email":{"fromName":"No Reply <[email protected]>","fromAddress":"[email protected]"}}]

It looks like I need to use FormArray to send the proper shape:

messaging: new FormArray([
    new FormGroup({
        email: new FormGroup({
            fromName: new FormControl(this.location.messaging[0].email.fromName, [Validators.required]),
            fromAddress: new FormControl(this.location.messaging[0].email.fromAddress, [Validators.required]),
        }),
    }),
]),

This is causing some trouble in my template as I'm currently doing this: form.get('messaging[0].email.fromAddress')

Resulting in:

Error: Cannot find control with path: 'messaging -> email'

I think I need to somehow loop through the FormArray. This really isn't a dynamic array though. I'll always have email and only fromName and fromAddress.

CodePudding user response:

Yes, you need the FormArray as the messaging is an array.

You need to iterate every element in FormArray via *ngFor and provide the index (i):

form.get(['messaging', i, 'email', 'fromName'])

The complete flow of your template form from parent FormGroup to fromName FormControl would be:

form (FormGroup) --> messaging (FormArray) --> i (FormGroup) --> email (FormGroup) --> fromName (FormControl)

The HTML template should be:

<div [formGroup]="form">
  <div
    formArrayName="messaging"
    *ngFor="let control of messaging.controls; let i = index"
  >
    <ng-container [formGroupName]="i">
      <div formGroupName="email">
        ...
        <div
          [ngClass]="
            form.get(['messaging', i, 'email', 'fromName'])!.errors &&
            (form.get(['messaging', i, 'email', 'fromName'])!.dirty ||
              form.get(['messaging', i, 'email', 'fromName'])!.touched)
              ? 'red'
              : 'green'
          "
        >
          <input formControlName="fromName" />
        </div>
        <div>
          <!-- fromAddress -->
          <input formControlName="fromAddress" />
        </div>
      </div>
    </ng-container>
  </div>
</div>
get messaging(): FormArray {
  return this.form.get('messaging') as FormArray;
}

Demo @ StackBlitz

  • Related