In this article, I will show angular reactive form examples with step-by-step explanations. The explanation contains FormGroup, FormControl, FormArray, form submission, a complete form example. So let’s start
What’s the angular reactive forms?
Reactive forms are form structures which define in the component class. Reactive forms are built with the model-driven approach to handling HTML form inputs values change over time. Reactive forms provide input validation in the component classes.
Some of the advantages of reactive forms:
- Two way data binding
- Dynamically adding form fields
- Dynamically changing validation
- Using custom validators
How to use reactive forms?
- Import built-in
ReactiveFormsModule
module - Define form model in component class using angular built-in forms blocks FormGroup, FormControl, FormArray
- Define the HTML form that resembles the form model.
- Bind model form in HTML with built-in
formGroup
attribute. - Bind model forms controls in HTML with
formControlName
attribute. - Define submit method in component class and declare in HTML form attribute
(ngSubmit)
How to use reactive forms blocks?
FormGroup block
The FormGroup
is a building block to define forms groups in the angular component class. FormGroup contains a collection of the child as FormControl, FormArray, and nested FormGroup. FormGroup takes the first argument collection of keys controls name which contain FormControl or FormArray or nested FormGroup. Example of FormGroup …
this.userForm = new FormGroup({ // FormControll and FormArray come here })
Associate FormGroup into HTML form
Angular reactive forms provide formGroup
an attribute to bind angular reactive form to template form
<form [formGroup]="userForm"> ... </form>
FormControl block
The FormControl
is the built-in class that contains a value (initial state) as the first parameter, validations as the second parameter, and custom async validations as the third parameter. Each input of form should have bound each FormControl
this.userForm = new FormGroup({ name: new FormControl(), email: new FormControl('', [Validators.required, Validators.email]), number: new FormControl('', Validators.required), password: new FormControl('', [Validators.required, Validators.minLength(6)]) })
Associate FormControl into HTML form
<form [formGroup]="userForm"> <input type="text" formControlName="name" placeholder="Name"> <input type="email" formControlName="email" placeholder="email"> <input type="number" formControlName="number" placeholder="number"> <input type="password" formControlName="password" placeholder="password"> </form>
FormArray block
The FormArray is a built-in block that manages the collection. The collection/FormArray block can contain FormControl, FormGroup, FormArray
this.userForm = new FormGroup({ ... hobbies: new FormArray([ new FormControl('', [Validators.required]) ]), skills: new FormArray([ new FormGroup({ name: new FormControl(''), yearOfExperience: new FormControl(''), }) ]) ... })
Associate FormArray into HTML form
Declare the FormArray controller property separately in the component class.
get skillsControls(): any { return (<FormArray>this.userForm.get('skills')).controls; }
Now bind angular FormArray in the HTML template
<div formArrayName="skills"> <div *ngIf="skillsControls && skillsControls.length"> <div *ngFor="let exception of skillsControls; let i = index" [formGroupName]="i"> <input formControlName="name" type="text" placeholder="Name" /> <input formControlName="yearOfExperience" type="text" placeholder="Year of Experiences" /> <hr> </div> </div> </div>
Access reactive form data
Angular reactive forms provide value property to access the value.
this.userForm.value // as form value this.userForm.get('name').value // as specific control value of form <formControl>.value // as name control value
access value via Observable
Angular reactive forms provide another methods valueChanges
property which is an Observable that can trigger when form any fields value changes.
this.userForm.valueChanges.subscribe(data => { console.log('formData', data) })
access with async pipe
{{userForm.valueChanges | async | json}}
How to submit reactive form?
To submit an angular form you need to define a submit method in the component class.
onFormSubmit(): void { const formData = this.userForm.value console.log(formData) // Call api post service here }
Now emit the template form submit an event with the onFormSubmit method. Angular provides built-in template ngSubmit EventEmmeter to bind template form to submit with component submit method.
Note: Make sure your form has a button with type submit, which is trigger template form submit an event. it’s a good approach to submit a form.
<form [formGroup]="userForm" (ngSubmit)="onFormSubmit()"> ... <button type="submit">Submit</button> </form>
You can reactive form submit manually with button click event
<button type="button" (click)="onFormSubmit()">Submit</button>
What is angular FormBulder class?
The FormBuilder
provides syntactic sugar that shortens creating instances of a FormControl
, FormGroup
, or FormArray
. It reduces the amount of boilerplate needed to build complex forms
Angular FormBuilder provides shortly creating instances of FormControl, FormGroup, FormArray, Which reduces repeated codes.
Checkout Angular FormBuilder Example:
Complete angular reactive form example step-by-step
For this example, I will create angular reactive userForm with row angular form method (FormGroup, FormControl, FormArray) and I will also create userForm with FormBuilder class
Step-1 Setting up new project
Generate new angular project
ng new angular-reactive-form --routing=true --style=scss --skip-tests cd angular-reactive-form
This will generate a new angular project with routing, the style set to scss (available style CSS, SASS, SCSS, LESS), and skip test configuration.
Import ReactiveFormsModule modules
The reactive forms require the import ReactiveFormsModule
module. Update app.module.ts file, add ReactiveFormsModule module.
import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, ReactiveFormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
If you are using SharedModule import in ShareModule. it’s best to use and practice
Step-2 Building userForm in component class
We can build reactive form two ways…
- Directly reactive form methods FormGroup, FormControl, FormArray.
- Build with FormBuilder class who is provide FormGroup, FormControl, FormArray method. Recommanded form complex form
Update app.component.ts file. define userForm
instance of FormGroup
.
Method-1 Create userForm with direct form method import
import { Component, OnInit } from '@angular/core'; import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit { userForm!: FormGroup; constructor() {} ngOnInit(): void { this.userForm = new FormGroup({ name: new FormControl(), email: new FormControl('', [Validators.required, Validators.email]), number: new FormControl('', Validators.required), password: new FormControl('', [Validators.required, Validators.minLength(6)]), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), zip: new FormControl('') }), skills: new FormArray([ this.newSkillGroup() ]) }) } get skillsControls(): any { return (<FormArray>this.userForm.get('skills')).controls; } newSkillGroup(): FormGroup { return new FormGroup({ name: new FormControl('', Validators.required), yearOfExperience: new FormControl('', Validators.required), }); } addSkillGroup(): void { (<FormArray>this.userForm.get('skills')).push(this.newSkillGroup()); } removeSkillGroup(index: number): void { (<FormArray>this.userForm.get('skills')).removeAt(index); } onFormSubmit(): void { const formData = this.userForm.value console.log(formData) // Call api post service here } }
Method-2 create userForm with FormBuilder class
import { Component, OnInit } from '@angular/core'; import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit { userForm!: FormGroup; constructor(private formBuilder: FormBuilder) {} ngOnInit(): void { this.userForm = this.formBuilder.group({ name: [], email: ['', [Validators.required, Validators.email]], number: ['', Validators.required], password: ['', [Validators.required, Validators.minLength(6)]], address: this.formBuilder.group({ street: [], city: [], zip: [] }), skills: this.formBuilder.array([ this.newSkillGroup() ]) }) } get skillsControls(): any { return (<FormArray>this.userForm.get('skills')).controls; } newSkillGroup(): FormGroup { return this.formBuilder.group({ name: ['', Validators.required], yearOfExperience: ['', Validators.required] }); } addSkillGroup(): void { (<FormArray>this.userForm.get('skills')).push(this.newSkillGroup()); } removeSkillGroup(index: number): void { (<FormArray>this.userForm.get('skills')).removeAt(index); } onFormSubmit(): void { const formData = this.userForm.value console.log(formData) // Call api post service here } }
The userForm
contain four controls as name, email, number, password. Each controls provided the initial empty value. But email, number, and password is required validation provided. and contain “address” as another FormGroup, “skills” as FormArray
skills as FormArray
newSkillGroup
contain skills form which has two instances of FormControl as name and yearOfExperience
In this component class defined skillsControls
property which is an instance skills
FormArray of userForm form. The skillsControls
property helps to bind template skills form inputs with userForm skills control.
addSkillGroup
and removeSkillGroup
the method provides dynamically add and removes skills FormArray item.
Learn more about dynamic reactive form control
Submit form method
OnFormSubmit
method responsible for the form to API
Step-3 Add userForm in component template
Update app.component.html file
<div class="container"> <form [formGroup]="userForm" (ngSubmit)="onFormSubmit()"> <h3>User Form</h3> <div class="input-group"> <input type="text" formControlName="name" placeholder="Name"> </div> <div class="input-group"> <input [class.border-danger]="userForm.get('email')?.touched && userForm.get('email')?.invalid" type="email" formControlName="email" placeholder="email"> <div *ngIf="userForm.get('email')?.touched && userForm.get('email')?.hasError('required')" class="text-sm text-danger">Email is required</div> <div *ngIf="userForm.get('email')?.touched && userForm.get('email')?.hasError('email')" class="text-sm text-danger">Email is not valid</div> </div> <div class="input-group"> <input [class.border-danger]="userForm.get('number')?.touched && userForm.get('number')?.invalid" type="number" formControlName="number" placeholder="number"> <div *ngIf="userForm.get('number')?.touched && userForm.get('number')?.hasError('required')" class="text-sm text-danger">Number is required</div> </div> <div class="input-group"> <input [class.border-danger]="userForm.get('password')?.touched && userForm.get('password')?.invalid" type="password" formControlName="password" placeholder="password"> <div *ngIf="userForm.get('password')?.touched && userForm.get('password')?.hasError('required')" class="text-sm text-danger">Password is required</div> <div *ngIf="userForm.get('password')?.touched && userForm.get('password')?.hasError('minlength')" class="text-sm text-danger">Password min 6 character required</div> </div> <hr> <h4>Address</h4> <div formGroupName="address"> <div class="input-group"> <input type="text" formControlName="street" placeholder="Street"> </div> <div class="input-group"> <input type="text" formControlName="city" placeholder="City"> </div> <div class="input-group"> <input type="text" formControlName="zip" placeholder="Zip Code"> </div> </div> <hr> <h4>Skills</h4> <div formArrayName="skills"> <div *ngIf="skillsControls && skillsControls.length"> <div *ngFor="let skill of skillsControls; let i = index" [formGroupName]="i"> <div class="input-group"> <input [class.border-danger]="skill.get('name')?.touched && skill.get('name')?.invalid" formControlName="name" type="text" placeholder="Name" /> <div *ngIf="skill.get('name')?.touched && skill.get('name')?.hasError('required')" class="text-sm text-danger">Skill Name is required</div> </div> <div class="input-group"> <input [class.border-danger]="skill.get('yearOfExperience')?.touched && skill.get('yearOfExperience')?.invalid" formControlName="yearOfExperience" type="text" placeholder="Year of Experiences" /> <div *ngIf="skill.get('yearOfExperience')?.touched && skill.get('yearOfExperience')?.hasError('required')" class="text-sm text-danger">Year of experiences required</div> </div> <div class="flex justify-end"> <button (click)="removeSkillGroup(i)" type="button">remove</button> </div> <hr> </div> </div> <div class="flex justify-end"> <button (click)="addSkillGroup()" type="button"> Add new item </button> </div> </div> <button [disabled]="userForm.invalid" style="margin-top: 40px;" type="submit">Submit</button> </form> </div>
Step-4 Add a few styles (optional)
Update app.component.scss file.
* { box-sizing: border-box; } .container { max-width: 400px; margin-left: auto; margin-right: auto; padding-top: 50px; } .text-sm { font-size: 80%; margin-top: 5px; } .border-danger { border-color: red; } .text-danger { color: red; } input { width: 100%; padding: 5px 12px; } .flex { display: flex; } .items-center { align-items: center; } .justify-end { justify-content: flex-end; } // form .input-group { margin-bottom: 10px; }