Smarter ideas worth writing about.

Intro to Angular's Reactive Forms

Forms are an inevitable part of any front-end development project – it’s the basis of how applications collect data. Angular attempts to simplify the form creation process by providing two different approaches to tackling form development, template-driven forms and reactive forms. What’s the difference? Template-driven forms are managed in the HTML template, while reactive forms are managed in the component.

When to use one over the other? Template-driven is good for basic forms with a fixed number of inputs; and validation of the entire form, instead of each individual input. Reactive-driven forms are best for complex forms with a dynamic number of inputs, validation of each input, and good for projects that require unit testing. Learn more about the pros and cons of template-driven forms and reactive forms.

As the title eludes to, we’ll be looking at reactive forms. Why reactive forms? I have experienced the power of reactive forms working on client projects and how relatively simple it is to develop complex forms. I wanted to share how you can use reactive forms in your next Angular project.

For you to get the most out of this post, I recommend that you have some basic Angular 2+ knowledge. For an intro to the Angular Methodology, including basic application development with the Angular 2 framework, check out our Getting Started with Angular 2 webinar.

During this step-by-step guide, the technologies we’ll use are TypeScript and Angular’s CLI (to use the CLI, you’ll need Node/npm installed, which we’ll cover as we go along). While the example form we’re building doesn’t have submit functionality, it highlights the basics of Angular’s reactive forms, including the following capabilities:

  • Validation for required fields, proper email formatting
  • Showing / hiding validation messages
  • Dynamically adding/removing CSS classes based on validations
  • Disabling / enabling the Submit button
  • Resetting the form

Let's get started.

Project Set up with Angular CLI

To simplify the set-up process, we’ll leverage Angular CLI. To install the CLI, if you don’t have it already, follow along with Angular’s documentation. Paraphrasing the documentation:

  • Ensure you have Node 6.9+ and NPM 3+ installed. You can check by entering the following commands in your Command Prompt / Terminal. If you find they need to be installed or updated, check out https://nodejs.org/en/.

    node-v

    npm-v

  • After installing / updating Node and npm, we’ll need to install TypeScript, and the Angular CLI
    npm intall -g typescript @angular/cli

Creating and Serving the Project

In the Command Prompt, navigate to a directory you’d like your project to live, and create the project by entering:
ng new reactive-forms-app

The CLI will install all the appropriate JavaScript packages for you. Once the packages have been installed, you need to run the project. Using the Command Prompt, change your directory to the reactive-forms-app, and run the following command:
ng serve OR npm start

The application should be running now, check it out, unless you’ve specified a different port. After saving changes you make during development, the browser will refresh automatically.

Creating the Component

Using Angular CLI to Create the Component

We’re ready to make the component, the CLI will create the template, component, CSS, and test files for you. Inside the root of the project’s directory enter:
ng generate component reactive-form

Reference Form Modules and the New Component

Because we’re using Angular forms, we must import the appropriate modules for our project in the app.module.ts file, located in the app directory. The modules we need are: FormsModule and ReactiveFormsModule from @angular/forms. In the @NGModule decorator, we need to reference the modules we just imported in the file. We need to place them inside of the imports array. We also need to reference the reactive-form component we just created. Open your favorite code editor and paste in the code below.

app.module.ts.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { ReactiveFormComponent } from './reactive-form/reactive-form.component';

@NgModule({
  declarations: [
    AppComponent,
    ReactiveFormComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Updating the Template

After the CLI generates the component, we’ll plug in the HTML for the form in the reactive-form.comonent.html file, which can be found below.

reactive-form.component.html

 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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<form novalidate [formGroup]="bookForm">

  <h1>Reactive Form Example</h1>

  <div class="group">
    <label for="title">Book Title * </label>
    <input type="text"
      [class.error]="!bookTitle.valid && bookTitle.touched"
      [formControl]="bookTitle"
      id="title" 
      placeholder="Book Title" 
      required>
      <div *ngIf="bookTitle.touched">
        <p *ngIf="bookTitle.hasError('required')">Field is required.</p>
        <p *ngIf="bookTitle.hasError('maxlength')">
          Book title must be less than {{maxBookLength}} characters.</p>
      </div>
  </div>

  <div class="group">
    <label for="author-first-name">Author's First Name *</label>
    <input type="text"
      [formControl]="firstName"
      [class.error]="!firstName.valid && firstName.touched"
      id="author-first-name" 
      class="medium" 
      placeholder="Author First Name" 
      required>
      <p *ngIf="!firstName.valid && firstName.touched">Field is required.</p>
  </div>

  <div class="group">
    <label for="author-last-name">Author's Last Name *</label>
    <input type="text" 
      [formControl]="lastName"
      [class.error]="!lastName.valid && lastName.touched"
      id="author-last-name" 
      class="medium" 
      placeholder="Author Last Name" 
      required>
      <p *ngIf="!lastName.valid && lastName.touched">Field is required.</p>      
  </div>

  <div class="group">
    <label for="author-email-address">Author's Email</label>
    <input type="email" 
      [formControl]="emailAddress"
      id="author-email-address"
      placeholder="Author Email"
      [class.error]="!emailAddress.valid && emailAddress.touched" 
      required>
        <p *ngIf="emailAddress.touched&& emailAddress.hasError('invalidEmail')">Enter a valid email address.</p>
    </div>

  <div class="group">
    <label for="genre">Genre *</label>
    <input type="text"
      [class.error]="!genre.valid && genre.touched"
      [formControl]="genre" 
      id="genre" 
      placeholder="Genre" 
      required>
    <p *ngIf="!genre.valid && genre.touched">Field is required.</p>
  </div>

  <div class="checkbox">
    <label>
      <input type="checkbox" [formControl]="bookRead" value="true">Read</label>
  </div>
  <br>
  <br>
  <p><strong>*</strong> Indicates a required field.</p>
  <hr>
  <button type="button" [disabled]="!bookForm.valid">Submit</button>
  <button class="clear" (click)="initializeForm()" type="button">Clear</button>

</form>

Updating the Component TypeScript File

We’ll talk about what’s going on in the other elements of the template, but for now we’ll cover the crux of Angular Reactive Forms, the component. Below is the completed reactive-form.component.ts file.

reactive-form.component.ts

 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
45
46
47
48
49
50
51
52
53
54
55
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators, AbstractControl, FormControl } from '@angular/forms';

@Component({
  selector: 'app-reactive-form',
  templateUrl: './reactive-form.component.html'
})
export class ReactiveFormComponent implements OnInit {

  constructor(private fb: FormBuilder) { }

  bookForm: FormGroup;
  bookTitle: AbstractControl;
  firstName: AbstractControl;
  lastName: AbstractControl;
  emailAddress: AbstractControl;
  genre: AbstractControl;
  bookRead: AbstractControl;

  maxBookLength: number = 50;

  ngOnInit() {
    this.initializeForm();
  }

  initializeForm() {
    this.bookForm = this.fb.group({
      bookTitle: ['Anything in here will display inside the input', Validators.compose([
        Validators.required,
        Validators.maxLength(this.maxBookLength)
      ])],
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      emailAddress: ['', Validators.compose([
        Validators.required,
        this.emailValidator
      ])],
      genre: ['', Validators.required],
      bookRead: [true]
    });
    this.bookTitle = this.bookForm.controls['bookTitle'];
    this.firstName = this.bookForm.controls['firstName'];
    this.lastName = this.bookForm.controls['lastName'];
    this.emailAddress = this.bookForm.controls['emailAddress'];
    this.genre = this.bookForm.controls['genre'];
    this.bookRead = this.bookForm.controls['bookRead'];
  }

  emailValidator(control: FormControl): { [s: string]: boolean } {
    if (!control.value.match(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/)) {
        return { invalidEmail: true };
    }
  }

}

To tie our form into Angular, we need to import the appropriate classes into the component from @angular/forms, specifically:

FormGroup, FormBuilder, Validators, AbstractControl and FormControl.

After the import, we need to inject FormBuilder in the constructor. FormBuilder does the heavy lifting for us in reactive forms. I’m using Angular’s recommended method for injecting FormBuilder into our component, and I’m naming it “fb” to reference it throughout the component.

We’re initializing the form inside of the Angular’s ngOnInit lifecycle hook. I made initializeForm() a method so when the user clicks the “Clear” button, the form is reset to its initial state.

Let’s cover what else is going on in the component.

FormGroup

Stores a reference to the form so we can use it throughout the component, this is the same name that we used as the formGroup attribute on the form element.

AbstractControl

Allows us to reference an input. This is best explained by an example, without the AbstractControl, we’d have to reference our inputs in the validation messages as:

*ngIf=!bookForm.controls['bookTitle'].valid && bookForm.controls['bookTitle'].touched 

With the AbstractControl, we can concisely reference the input as follows:

*ngIf=!bookTitle.valid && bookTitle.touched


Validators

Gives us access to Angular’s built-in validators, and allows us to create our own and reference them in the template, for example:

<p *ngIf="bookTitle.hasError('required') && bookTitle.touched">Field is required.</p>
<p *ngIf="bookTitle.hasError('maxlength') && bookTitle.touched">
          Book title must be less than {{maxBookLength}} characters.
</p>

The parameter of this.fb.group method accepts an object, where each formControl is a key, and the property of the key is an array. At the zero index, the string is the default value of the input, and the first index, we’re referencing the validators. If you have multiple validations for an input, you must use the compose method on the Validators class.

this.bookForm = this.fb.group({
bookTitle: ['Anything in here will display inside the input', Validators.compose([
        		Validators.required,
       		Validators.maxLength(this.maxBookLength)
      	])],
               	
 });

Toggling Validation Messages and CSS Classes

We’ll only show validation error messages if there’s an error, and if the user “touched” the input. We covered this when we talked about AbstractControls, but we’ll dig in a little deeper in validations.

Thanks to the FormBuilder, our inputs (FormControls) have access to a variety of methods and properties, specifically valid, touched and hasError():

  • formControlName.valid: checks the validity of the input
  • formControlName.touched: has the user touched and navigated away from the input
  • formControlName.hasError(): checks for different errors, this method accepts built-in and custom validators, we created our custom email validator, called “invalidEmail”

We can also toggle CSS classes based on the valid and touched properties available, for example, we’re only adding the error class to the input if the field is invalid and the field was touched:

[class.error]=!formControlName.valid && formControlName.touched

Disabling the Submit Button and Clearing the Form

Just like our input fields have properties and methods, FormBuilder provides similar functionality to the form itself. Using these properties is how we disable the Submit button, until the fields are valid.

[disabled]=!bookForm.valid

To clear or reset the form, we call the initializeForm method on the click event.

(click)=initializeForm

The Final Step: Updating the app.component.html file

Open to the app.component.html file reference the element/component inside of the app.component.html. In the file, you should just have:

<app-reactive-form></app-reactive-form>

Once you’ve saved the changes to the app.component.html file, you should have a working form!

Tips and When Developing Reactive Forms

When you’re on your own developing an Angular reactive form, remember the following tips to avoid snafus, and make development a little easier:

  • Ensure the values for the formGroup and formControl attributes in the HTML, match the variable names in your TypeScript file.
  • Import the FormsModule and ReactiveFormsModule in your project.
  • Using the AbstractControl type will make it easier to reference your form inputs.
  • Only displaying validation error messages if the user touched the input, and there’s an error; otherwise, your user will see validation error messages when the form loads.

Hopefully you find the value of using reactive forms and you will incorporate them in your next Angular project.

Share:

About The Author

App Dev Consultant

John is a professional web developer and designer in Cardinal's Raleigh office. His passion for web development and web design makes it natural for him to go above and beyond required expectations while working collaboratively with colleagues. As his experience grows, John is also dedicated to enhancing his knowledge and skills in web development and design.