Tutorial

How To Use Reactive Forms in Angular

Angular

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:

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 FormControls 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.

Creative Commons License