Reactive form is one of the two types of Angular forms; the other is template-driven forms. Both serve the same purpose, but there are key differences between them.
Template-driven Forms:
- We mostly use HTML for development.
- They rely on Angular directives like
ngForm
,ngSubmit
, andngModel
. - Best suited for simple forms.
Reactive Forms:
- Creation and management are done in the component class (e.g.,
example.component.ts
file). - Form controls and their validation are explicitly defined using
FormBuilder
,FormControl
, andFormGroup
. - More powerful and capable of handling complex forms.
This blog post is not another tutorial about creating reactive forms and controls; rather, it is a collection of common challenges and best practices I encountered while coding these forms.
Reactive Form Groups and Form Controls:
The best practice for creating FormControls
is to use either FormBuilder
or FormGroup
. Both approaches are effective, but with FormGroup
, all the FormControls
are initiated explicitly.
public myFormGroup:FormGroup;
constructor(private fb:FormBuilder) {
}
ngOnInit(){
this.myFormGroup = this.fb.group({
'firstName': this.fb.control(''),
'lastName': this.fb.control(''),
'age': this.fb.control(''),
})
}
In contrast, with FormBuilder
, not all FormControls
need to be explicitly initiated, making it suitable for large, complex forms.
public myFormGroup:FormGroup;
constructor() {
}
ngOnInit(){
this.myFormGroup = new FormGroup({
'firstName': new FormControl(''),
'lastName': new FormControl(''),
'age': new FormControl(''),
})
}
If you are creating a complex form and need to insert a new FormControls
dynamically, consider using FormBuilder
.
<!--HTML PART--!>
<form [formGroup]="myFormGroup">
<div>
<label> First Name </label>
<input type="text" formControlName="firstName"/>
</div>
<div>
<label> Last Name </label>
<input type="text" formControlName="lastName"/>
</div>
<div>
<label> Age </label>
<input type="number" formControlName="age"/>
</div>
</form>
Initializing Controls in Reactive Forms
Use best practices when initializing form controls. For text fields, initialize with an empty string; for number fields, start with 0
, and for radio form controls, set to false
or true
.
initFormControls() {
this.myFormGroup = this.fb.group({
'firstName': this.fb.control('', Validators.required),
'age' : this.fb.control('', Validators.required),
'status' : this.fb.control(true, Validators.required),
'country' : this.fb.control(null, Validators.required)
})
}
Here country is the dropdown so, the initial value is null.
Reactive Form Fun Fact
If you are using ng-select
Here is a simple guide to learning more about ng-select. give null
as the initial value for its form control; otherwise, the input placeholder won’t be visible.
Setting Values for Reactive Form Controls:
There are two ways to set values for a form control or form group: setValue
and patchValue
. You can use setValue
to assign values to all the form controls present in the form group, but this method only works if all the form fields have values. Otherwise, it will throw an error.
let mydetails = {
'firstName': 'Zenitsu',
'lastName': 'Agatsuma',
'age': 22,
'status':true,
'country': 'Japan'
};
this.myFormGroup.setValue(mydetails);
If you don’t have all the form field values in the object, you can use patchValue
to update the form group.
this.myFormGroup.patchValue({'age':25, 'status':false});
You can also use setValue
for individual form controls.
this.myFormGroup.get('age').setValue(30);
The Magic of Value Changes:
We can monitor and perform operations based on input value changes using this feature. Value changes are triggered when the form control value changes, including when setValue
patchValue
methods are called.
this.myFormGroup.valueChanges.subscribe((formData:any)=> {
console.log(formData) //consoles all the form control values whenever its changed.
})
You can add extra options inside the setValue
and patchValue
methods to avoid triggering value changes unnecessarily.
this.myFormGroup.get('status').setValue(false, {emitEvent:false});
Nested Form Groups:
Nested form groups can be the most complex aspect of reactive forms. A basic understanding is that you can create a form group inside another form group.
initFormControls() {
this.myFormGroup = this.fb.group({
'firstName': this.fb.control('', Validators.required),
'age' : this.fb.control('', Validators.required),
'status' : this.fb.control(true, Validators.required),
'country' : this.fb.control(null, Validators.required),
'company' : this.fb.group({
'name': this.fb.control('', Validators.required),
'address': this.fb.control('', Validators.required)
})
})
}
This requires minor HTML adjustments. When adding form controls dynamically inside the nested form group, you first need to get the instance of the child form group and then add the form control.
addNewControl() {
let childForm = this.myFormGroup.get('company') as FormGroup;
childForm.addControl('contactNumber', Validators.required);
}
Based on your form’s needs, we can develop nested form groups in multiple ways, and here are some examples.
<form [formGroup]="myFormGroup">
<div>
<label> First Name </label>
<input type="text" formControlName="firstName"/>
</div>
<div>
<label> Last Name </label>
<input type="text" formControlName="lastName"/>
</div>
<div>
<label> Age </label>
<input type="number" formControlName="age"/>
</div>
<ng-container formGroupName='company'>
<div>
<label> Company Name </label>
<input type="number" formControlName="name"/>
</div><div>
<label> Company Address</label>
<input type="number" formControlName="address"/>
</div>
</ng-container>
</form>
Form Arrays In Reactive Forms:
Form arrays are similar to nested form groups, but the key difference is that you can access the form controls inside the array using indexes, just like a normal array.
<form [formGroup]="dynamicForm">
<div formArrayName="items" *ngFor="let item of items().controls; let i = index">
<div [formGroupName]="i">
<input formControlName="name" placeholder="Name" />
<input formControlName="quantity" type="number" placeholder="Quantity" />
<button (click)="removeItem(i)">Remove</button>
</div>
</div>
<button (click)="addItem()">Add Item</button>
<button type="submit" (click)="submitForm()">Submit</button>
</form>
These have unique use cases and only cover limited scenarios.
dynamicForm: FormGroup;
constructor(private fb: FormBuilder) {
this.dynamicForm = this.fb.group({
items: this.fb.array([])
});
}
// Getter for items FormArray
items(): FormArray {
return this.dynamicForm.get('items') as FormArray;
}
// Method to add a new item FormGroup
addItem() {
const itemGroup = this.fb.group({
name: ['', Validators.required],
quantity: [0, [Validators.required, Validators.min(1)]]
});
this.items().push(itemGroup);
}
// Method to remove an item FormGroup
removeItem(index: number) {
this.items().removeAt(index);
}
// Method to handle form submission
submitForm() {
if (this.dynamicForm.valid) {
console.log(this.dynamicForm.value);
}
}
Conclusion
This briefly overviews how reactive forms work and the common hiccups encountered while using them. In later articles, we will explore each section of reactive forms in detail. Learn more about reactive forms and other useful topics about forms.