Avoid using the [(ngModel)]
directive! Reactive forms offer a more elegant solution. They render manual ngModel
bindings unnecessary, and come with several useful built-in features, a few of which I'll explain below.
Connecting to the form
When connecting to a form control like a text input, utilize this template syntax:
<ng-container [formGroup]="this.myFormGroup">
<input type="text" formControlName="field1">
<input type="text" formControlName="field2">
<ng-container formGroupName="subgroupName">
<input type="text" formControlName="subfield2">
</ng-container>
<input type="text" formControlName="myRequiredField">
</ng-container>
(field1
, field2
, subgroupName
, subfield2
, and myRequiredField
are arbitrary names corresponding to elements in your form, as explained when creating the FormGroup
object.)
Note on <ng-container>
: Instead of <ng-container>
, you can use any other tag that fits better semantically. For instance,
<form [formGroup]="this.myFormGroup">
. I opted for <ng-container>
here because it doesn't introduce an extra HTML element upon rendering; <ng-container><div /></ng-container>
displays as just a <div/>
in the DOM tree. This is beneficial if your CSS relies on specific tag structures.
Read-only data bindings to the model within the FormGroup
are accessed differently in your template:
{{ this.myFormGroup.get('field1').value }}
{{ this.myFormGroup.get('subgroupName.subfield2').value }}
<!-- Tip: Use an array for field names containing "." -->
{{ this.myFormGroup.get(['subgroupName', 'subfield2']).value }}
Setting up the FormGroup
In your component class, within the constructor()
(before the template is rendered), build a form group to link to your form using this syntax:
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
...
public readonly myFormGroup: FormGroup;
...
constructor(private readonly formBuilder: FormBuilder) {
this.myFormGroup = this.formBuilder.group({
field1: [],
field2: [],
subgroupName: this.formBuilder.group({
subfield2: [],
}),
myRequiredField: ['', Validators.required],
});
this.retrieveData();
}
Populating your form with data
If your component requires data retrieval from a service during initialization, ensure that data transfer begins after the form is constructed, then use patchValue()
to insert the data from your object into the FormGroup
:
private retrieveData(): void {
this.dataService.getData()
.subscribe((res: SomeDataStructure) => {
// Assuming res matches the template structure
// e.g.:
// res = {
// field1: "some-string",
// field2: "other-string",
// subgroupName: {
// subfield2: "another-string"
// },
// }
// Values in res not aligning with the form structure
// are disregarded. You can also pass your own object.
this.myFormGroup.patchValue(res);
});
}
Retrieving data from the form
After the user submits the form and you need to extract the data to send back to your API via a service, use getRawValue
:
public onClickSubmit(): void {
if (this.myFormGroup.invalid) {
// Stop if invalid
alert('Invalid input');
return;
}
this.myDataService.submitUpdate(this.myFormGroup.getRawValue())
.subscribe((): void => {
alert('Saved!');
});
}
By employing these methods, there's no need for [(ngModel)]
bindings, as the form maintains its internal model within the FormGroup
object.