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 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 group(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 <noreply@example.com>', fromAddress: 'noreply@example.com'}
I've tried various methods of messaging['email']
or messaging.email
messaging[0]
but I can't 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 correctly read/present the data?
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 <noreply@example.com>","fromAddress":"noreply@example.com"}}]
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
.
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;
}