How can I create a nested reactive form group to mimic the shape of data?

Viewed 17

I am trying to create a reactive form & up until I've started working with multiple Form Groups, things have been going really well.

I have a field in my database that stores a json object, it looks like this:

"messaging":[
      {
         "email":{
            "fromName":"No Reply <noreply@example.com>",
            "fromAddress":"noreply@example.com"
         }
      }
   ],

I am trying to create a few Form Group elements to help create that data shape in my front end code.

Here is what the model looks like:

// model.ts

messaging: [
    {
        email?: {
            fromName: string;
            fromAddress: string;
        };
    }
];

In my component, I have tried declaring the fields this way. Both as a FormGroup and/or as a FormControl.

fromName is the name of the field, but I thought given shape of my data, I thought using messaging and email made for good use of a FormGroup.

...
messaging: FormGroup;
email: FormGroup;
fromName: FormControl;
fromAddress: FormControl;

...

ngOnInit(): void {
    this.fromName = new FormControl(
            this.location?.messaging[0].email?.fromName,
            [Validators.required]
        );

    ...

    this.form = new FormGroup({
        ...

        messaging: new FormGroup({
            email: new FormGroup({
                fromName: this.fromName,
                fromAddress: this.fromAddress,
            }),
        }),
    });

}

My template looks like this:

...
<div [formGroup="form">
...
<div [formGroup]="messaging">
    <fieldset>
        <legend>Messaging</legend>
        <div [formGroup]="email">
            <input
                id="fromName"
                type="text"
                name="fromName"
                autocomplete=""
                placeholder="No Reply <noreply@example.com>"
                formControlName="fromName"
                [ngClass]="email.controls['fromName'].errors &&(email.controls['fromName'].dirty || email.controls['fromName'].touched) ? 'text-red-900 placeholder-red-300' : 'text-gray-900 placeholder-gray-300'" required />
        </div>

...

The error I am getting is

Cannot read properties of undefined (reading 'enabled')

If I change [formGroup]="messaging" to be formGroupName="messaging" I get an error that:

formGroup expects a FormGroup instance. Please pass one in.

I don't believe I am structuring my component file correctly. According to the docs I think I've nested things correctly, but clearly I'm missing an important step.

How can I structure my (nested) reactive form properly?

1 Answers

It looks like you might be making more work than it's worth.

To recap, here is what (a part) the model looks like:

// model.ts
...
messaging: [
    {
        email?: {
            fromName: string;
            fromAddress: string;
        };
    }
];
...

Here is where things start to differ. Your component should look more like this:

// component.ts

...

form: FormGroup;

constructor() {}

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]
                    ),
        }),
    }),
    
}

...

And finally, here is what an example of the html template might look like:

// component.html

<input id="fromAddress" 
    type="email" 
    name="fromName" 
    autocomplete="" 
    class="block w-full border-0 p-0 transition duration-75 ease-linear focus:ring-0 sm:text-sm" 
    placeholder="noreply@example.com"
    formControlName="fromName" 
    [ngClass]="form.get([
        'messaging', 'email', 'fromAddress'])!.errors &&
    (form.get(['messaging','email','fromAddress'])!.dirty || 
    form.get(['messaging','email', 'fromAddress'])!.touched)
    ? 'text-red-900 placeholder-red-300'
    : 'text-gray-900 placeholder-gray-300'"
    required
/>
Related