Reactive forms ( also known as Model-driven forms) is one of the two ways to build Angular forms. In this tutorial, we will learn how to build a simple Example Reactive Form. To build reactive forms, first, we need to import ReactiveFormsModule
. We then create the Form Model in component class using Form Group, Form Control & Form Arrays. Next, we will create the HTML form template and bind it to the Form Model.
Applies to: Angular 2 to the latest edition of i.e. Angular 8. Angular 9, Angular 10, Angular 11
Table of Content
What are Reactive Forms?
Reactive forms are forms where we define the structure of the form in the component class. I,e we create the form model with Form Groups, Form Controls, and Form Arrays. We also define the validation rules in the component class. Then, we bind it to the HTML form in the template. This is different from the template-driven forms, where we define the logic and controls in the HTML template.
How to use Reactive Forms
- Import
ReactiveFormsModule
- Create Form Model in component class using Form Group, Form Control & Form Arrays
- Create the HTML Form resembling the Form Model.
- Bind the HTML Form to the Form Model
Reactive Forms Example Application
Use ng new
to create a new application
1 2 3 | ng new mdf --routing=true --style=css |
Run ng serve
and verify if everything is installed correctly.
Import ReactiveFormsModule
To work with Reactive forms, we must import the ReactiveFormsModule
. We usually import it in root module or in a shared module. The ReactiveFormsModule
contains all the form directives and constructs for working with angular reactive forms.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, ReactiveFormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } |
Model
In the template-driven approach, we used ngModel
& ngModelGroup
directive on the HTML elements. The FormsModule
created the FormGroup
& FormControl
instances from the template. This happens behind the scene.
In Reactive Forms approach, It is our responsibility to build the Model using FormGroup
, FormControl
and FormArray
.
The FormGroup
, FormControl
& FormArray
are the three building blocks of the Angular Forms. We learned about them in Angular Forms Tutorial.
FormControl
encapsulates the state of a single form element in our form. It stores the value and state of the form element and helps us to interact with them using properties & methods.
FormGroup
represents a collection of form Controls. It can also contain form groups and form arrays. In fact, an angular form is a FormGroup
.
Let’s create the model for our Form.
First, we need to import FormGroup
, FormControl
& Validator
from the @angular/forms
. Open the app.component.ts
and the add following import statement.
1 2 3 | import { FormGroup, FormControl, Validators } from '@angular/forms' |
Best Angular Books
The Top 8 Best Angular Books, which helps you to get started with Angular
FormGroup
The FormGroup
is created with the following syntax
1 2 3 | contactForm = new FormGroup({}) |
The FormGroup
takes 3 arguments. a collection of a child FormControl
, a validator
, and an asynchronous validator
. The validators are optional.
FormControl
The first argument to FormGroup
is the collection of FormControl
. They are added using the FormControl
method as shown below
1 2 3 4 5 6 7 8 9 10 | contactForm = new FormGroup({ firstname: new FormControl(), lastname: new FormControl(), email: new FormControl(), gender: new FormControl(), isMarried: new FormControl(), country: new FormControl() }) |
In the above, we have created an instance of a FormGroup
and named it as contactForm
. contactForm
is our top-level FormGroup
. Under the contactForm
, we have five FormControl
instances each representing the properties firstname
. lastname
.email
, gender
, ismarried
& country
.
The Other two arguments to FormGroup
are Sync Validator
& Async Validator
. They are optional.
HTML Form
The next task is to build an HTML form. The following is a regular HTML form
. We enclose it in a <form>
tag. We have included two text input (FirstName & LastName), an email field, a radio button (gender), a checkbox (isMarried), and a select list (country). These are Form elements.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <form"> <p> <label for="firstname">First Name </label> <input type="text" id="firstname" name="firstname"> </p> <p> <label for="lastname">Last Name </label> <input type="text" id="lastname" name="lastname"> </p> <p> <label for="email">Email </label> <input type="text" id="email" name="email"> </p> <p> <label for="gender">Geneder </label> <input type="radio" value="male" id="gender" name="gender"> Male <input type="radio" value="female" id="gender" name="gender"> Female </p> <p> <label for="isMarried">Married </label> <input type="checkbox" id="isMarried" name="isMarried"> </p> <p> <label for="country">country </label> <select id="country" name="country"> <option [ngValue]="c.id" *ngFor="let c of countryList"> {{c.name}} </option> </select> </p> <p> <button type="submit">Submit</button> </p> </form> |
Binding the template to the model
Now we need to associate our model to the Template. We need to tell angular that we have a model for the form.
This is done using the formGroup
directive as shown below.
1 2 3 | <form [formGroup]="contactForm"> |
We have used the square bracket (one-way binding) around FormGroup
directive and set that equal the model.
Next, we need to bind form fields to the FormControl
models. We use the FormControlName
directive for this. We add this directive to every form field element in our form. The value is set to the name of the corresponding FormControl
instance in the component class.
1 2 3 4 | <input type="text" id="firstname" name="firstname" formControlName="firstname"> <input type="text" id="lastname" name="lastname" formControlName="lastname"> |
Submit form
We submit the form data to the component using the Angular directive named ngSubmit
. Note that we already have a submit
button in our form. The ngSubmit
directive binds itself to the click event of the submit
button. We are using event binding (parentheses) to bind ngSubmit
to OnSubmit
method. When the user clicks on the submit
button ngSubmit
invokes the OnSubmit
method on the Component class
1 2 3 | <form [formGroup]="contactForm" (ngSubmit)="onSubmit()"> |
Final Template
Our Final Template is as shown below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <form [formGroup]="contactForm" (ngSubmit)="onSubmit()"> <p> <label for="firstname">First Name </label> <input type="text" id="firstname" name="firstname" formControlName="firstname"> </p> <p> <label for="lastname">Last Name </label> <input type="text" id="lastname" name="lastname" formControlName="lastname"> </p> <p> <label for="email">Email </label> <input type="text" id="email" name="email" formControlName="email"> </p> <p> <label for="gender">Geneder </label> <input type="radio" value="male" id="gender" name="gender" formControlName="gender"> Male <input type="radio" value="female" id="gender" name="gender" formControlName="gender"> Female </p> <p> <label for="isMarried">Married </label> <input type="checkbox" id="isMarried" name="isMarried" formControlName="isMarried"> </p> <p> <label for="country">country </label> <select id="country" name="country" formControlName="country"> <option [ngValue]="c.id" *ngFor="let c of countryList"> {{c.name}} </option> </select> </p> <p> <button type="submit">Submit</button> </p> </form> |
Receive the data in the Component class
The last step is to receive the form data in the component class. All we need to do is to create the onSubmit
method in our component class.
1 2 3 4 5 | onSubmit() { console.log(this.contactForm.value); } |
We are using the console.log(this.contactForm.value)
to send the value of our form data to the console window.
Test the form
Now you can run the app and see the result. Open the developer console and see the value returned by the contactForm
.value. The values of the form are returned as JSON
object as shown below, which you can pass it your backend API to persist the information to the database.
FormControl
A FormControl
takes 3 arguments. a default value, a validator and an asynchronous validator. All of them are optional.
Default Value
You can pass a default value as either as a string or as an object of key-value pair. When you pass object you can set both the value and the whether or not the control is disabled.
1 2 3 4 | //Setting Default value as string firstname= new FormControl(‘Sachin’); |
1 2 3 4 | //Setting Default value & disabled state as object firstname: new FormControl({value: ‘Rahul’, disabled: true}), |
Sync Validator
The second parameter is an array of sync Validators. Angular has some built-in Validators such as required
and minLength
etc.
You can pass with Validator function as shown below.
1 2 3 | firstname: new FormControl('', [Validators.required,Validators.minLength(10)]), |
Asynchronous validator
The third argument is the Async Validator. The syntax of Async Validators is similar to Sync Validators.
More on validation in our next tutorial Validations in Reactive forms.
Grouping the controls using FormGroup
We can group various form controls together. For Example fields such as street, city, and Pincode each will have their own form control, but can be grouped together as an address form group
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | contactForm = new FormGroup({ firstname: new FormControl(), lastname: new FormControl(), email: new FormControl(), gender: new FormControl(), isMarried: new FormControl(), country: new FormControl(), address:new FormGroup({ city: new FormControl(), street: new FormControl(), pincode:new FormControl() }) }) |
In the code above, we have created new FormGroup
Address and added three form controls i.e city
, street
& Pincode
In the template use the formGroupName
directive to enclose the control using a div
element as shown below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <div formGroupName="address"> <div class="form-group"> <label for="city">City</label> <input type="text" class="form-control" name="city" formControlName="city" > </div> <div class="form-group"> <label for="street">Street</label> <input type="text" class="form-control" name="street" formControlName="street" > </div> <div class="form-group"> <label for="pincode">Pin Code</label> <input type="text" class="form-control" name="pincode" formControlName="pincode"> </div> </div> |
Summary
We learned how to build Angular Reactive Forms in this tutorial. In the next tutorial, we will add validation rules to our application.
- Angular Forms Tutorial: Fundamental & Concepts
- Template Driven Forms in Angular
- Set Value in Template Driven forms in Angular
- Reactive Forms in Angular
- FormBuilder in Reactive Forms
- SetValue & PatchValue in Angular
- StatusChanges in Angular Forms
- ValueChanges in Angular Forms
- FormControl
- FormGroup
- FormArray Example
- Build Dynamic or Nested Forms using FormArray
- Validations in Reactive Forms in Angular
- Custom Validator in Reactive Forms
- Passing Parameter to Custom Validator in Reactive Forms
- Inject Service into Custom Validator
- Validation in Template Driven Forms
- Custom Validator in Template Driven Forms
What if the html is loaded with custom element-based directives? Should there be a separate form model for each component? If so, how do you implement save functionality on the component with the submit button and have it cascade through all of the other form models?
If not, does that mean you nee done big form model containing knowledge of layout of all the individual components? That’s not very agile?
All of the examples I’ve been able to find are simple ones with all of the elements of the form contained in a single template.
Each component is isolated from each other and will have its own form model.
You can use a service to share the data between components and ensure that all the components stays in sync with the data from the service.
Whenever the submit button is pressed, you can call the save button in the service do update the data
Hope that answers your query