Introduction
Angular provides two ways to work with forms: template-driven forms and reactive forms (also known as model-driven forms). Template-driven forms are the default way to work with forms in Angular. With template-driven forms, template directives are used to build an internal representation of the form. With reactive forms, you build your own representation of a form in the component class.
Note: Reactive forms were introduced with Angular 2.
Here are some of the advantages of reactive forms:
Using custom validators
Changing validation dynamically
Dynamically adding form fields
In this article, you will explore how reactive forms can be applied to an example Angular application.
Prerequisites
If you would like to follow along with this article, you will need:
Node.js installed locally, which you can do by following How to Install Node.js and Create a Local Development Environment.
This post assumes you have some basic knowledge of Angular.
This post also assumes you are building from a fresh Angular project generated by @angular/cli
. You can refer to this post if you’re getting started with Angular CLI.
This tutorial was verified with Node v15.1.0, npm
v6.14.8, @angular/core
v11.0.0, and @angular/forms
v11.0.0.
Step 1 — Setting Up the Project
For the purpose of this tutorial, you will build from a default Angular project generated with @angular/cli
.
npx @angular/cli new angular-reactive-forms-example --style=css --routing=false --skip-tests
This will configure a new Angular project with styles set to “CSS” (as opposed to “Sass”, Less”, or “Stylus”), no routing, and skipping tests.
Navigate to the newly created project directory:
cd angular-reactive-forms-example
To work with reactive forms, you will be using the ReactiveFormsModule
instead of the FormsModule
.
Open app.module.ts
in your code editor amd add ReactiveFormsModule
:
src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
ReactiveFormsModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
At this point, you should have a new Angular project with ReactiveFormsModule
.
Step 2 — Adding a Form to the Component Template
With reactive forms, the logic is declared entirely in the component class.
Open app.component.html
in your code editor and add the following lines of code:
src/app/app.component.html
<form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)">
<div>
<label>
Name:
<input formControlName="name" placeholder="Your name">
</label>
</div>
<div>
<label>
Email:
<input formControlName="email" placeholder="Your email">
</label>
</div>
<div>
<label>
Message:
<input formControlName="message" placeholder="Your message">
</label>
</div>
<button type="submit">Send</button>
</form>
This code will create a form with three fields: name
, email
, and message
. There will also be a "submit"
button with the label "Send"
. When submitting the form, the method onSubmit(myForm)
will be called.
Note: If you are using Angular 2.x, you should also add the novalidate
directive with the opening form
tag, as Angular overrides HTML5’s validation. With Angular 4+, novalidate
is automatically added behind the scenes.
Let’s break it down:
formGroup
: The form will be treated as a FormGroup
in the component class, so the formGroup
directive allows to give a name to the form group.
ngSubmit
: This is the event that will be triggered upon submission of the form.
formControlName
: Each form field should have a formControlName
directive with a value that will be the name used in the component class.
At this point, you should have a new Angular project with a component template that uses a form.
Step 3 — Building the Component Class
Next, in the component class, you will define the FormGroup
and individual FormControl
s within the FormGroup
.
If a value is provided when newing a FormControl
, it will be used as the initial value for the field.
Notice how the FormGroup
and FormControl
names are the same that were used in the template. Also notice how you initialize the FormGroup
in the ngOnInit
lifecycle hook:
src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
myForm: FormGroup;
ngOnInit() {
this.myForm = new FormGroup({
name: new FormControl('Sammy'),
email: new FormControl(''),
message: new FormControl('')
});
}
onSubmit(form: FormGroup) {
console.log('Valid?', form.valid); // true or false
console.log('Name', form.value.name);
console.log('Email', form.value.email);
console.log('Message', form.value.message);
}
}
For the purposes of this tutorial, the onSubmit
method does not actually communicate the submitted form values to any external service or server. It serves to show how you can access the form’s validity and FormControl
values.
At this point, you can compile your application and open it in a web browser. After entering values for name
, email
, and message
and pressing Submit, the console log will display the values.
Step 4 — Updating the Component Class to Use FormBuilder
The ngOnInit
form construction can be rewritten with the FormBuilder
helper. This allows you to forgo of all the newing of form group and form controls.
Revisit app.component.ts
in your code editor and remove FormControl
and replace FormGroup
with FormBuilder
:
src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
myForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.myForm = this.fb.group({
name: 'Sammy',
email: '',
message: ''
});
}
onSubmit(form: FormGroup) {
console.log('Valid?', form.valid); // true or false
console.log('Name', form.value.name);
console.log('Email', form.value.email);
console.log('Message', form.value.message);
}
}
This code with FormBuilder
reduces the amount of boilerplate code for creating a FormGroup
.
Step 5 — Updating the Component Class to Use Validators
Add the Validators
class to your imports and declare your form controls with arrays instead of simple string values.
The first value in the array is the initial form value and the second value is for the validator(s) to use. Notice how multiple validators can be used on the same form control by wrapping them into an array.
RevisitRevisit app.component.ts
in your code editor and add Validators
:
src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
myForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.myForm = this.fb.group({
name: ['Sammy', Validators.required],
email: ['', [Validators.required, Validators.email]],
message: ['', [Validators.required, Validators.minLength(15)]],
});
}
onSubmit(form: FormGroup) {
console.log('Valid?', form.valid); // true or false
console.log('Name', form.value.name);
console.log('Email', form.value.email);
console.log('Message', form.value.message);
}
}
This code adds required
to the name
, email
, and message
fields. It also ensures the email
value uses the format of a valid email address. It also ensures that the message
value is at least 15 characters long.
If any of these form requirements are not passing, the valid
value will be false
. If all of these form requirements are passing, the valid
value will be true
.
Step 6 — Accessing Form Value and Validity in the Template
In the template, you can access each FormControl
’s value and validity and the value and validity of the whole form group as a whole.
Revisit app.component.html
and use *ngIf
to display feedback messages to the user if the form values are not valid:
src/app/app.component.html
<form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)">
<div>
<label>
Name:
<input formControlName="name" placeholder="Your name">
</label>
<div *ngIf="myForm.get('name').invalid && (myForm.get('name').dirty || myForm.get('name').touched)">
Please provide a name.
</div>
</div>
<div>
<label>
Email:
<input formControlName="email" placeholder="Your email">
</label>
<div *ngIf="myForm.get('email').invalid && (myForm.get('email').dirty || myForm.get('email').touched)">
Please provide a valid email address.
</div>
</div>
<div>
<label>
Message:
<input formControlName="message" placeholder="Your message">
</label>
<div *ngIf="myForm.get('message').invalid && (myForm.get('message').dirty || myForm.get('message').touched)">
Messages must be at least 15 characters long.
</div>
</div>
<button type="submit" [disabled]="myForm.invalid">Send</button>
</form>
This code checks to see if the user has interacted with the field (dirty
or touched
). Then, if the value is not passing the validation requirements, it will display the error message. The Send button will also be disabled until all the issues with the form values are addressed.
There are multiple ways to retrieve form control values. This example uses myForm.get('name')
which is equivalent to myForm.controls.name
. It is possible to retrieve error information with .hasError('required')
or .errors.required
.
Conclusion
In this article, you explored how reactive forms can be applied to an example Angular application. You used FormControl
, FormGroup
, FormBuilder
, and Validators
to construct an example form with validation. For additional functionality, consult the official documentation.
If you’d like to learn more about Angular, check out our Angular topic page for exercises and programming projects.