Angular Reactive Forms Tutorial

In this Angular tutorial we learn how to build forms in Angular with it's new model-driven reactive forms approach.

We also cover various built-in Angular form data validators.

What are Reactive Forms

Reactive forms is a model-driven approach to building forms in Angular.

With this approach, the model of the form is created in the component’s class and then bound to HTML elements in the view by using special markup.

Angular’s Forms module consists of three building blocks.

  • FormControl
  • FormGroup
  • FormArray

Lesson project setup

For this lesson there is no initial setup other than having an app.

To keep things simple, we will be working directly from inside the main ‘app.component’ component and will create whatever else we need on a step-by-step basis.

FormControl

A FormControl is a single input field in an Angular form.

As an example, let’s consider a simple text input.

Example:
Name: <input type="text" name="name" />

As a developer, we want to know a few things about this input.

  • What is the current value
  • Is the current value valid
  • Has the user changed the value

We would also like to be notified when the user changes the value.

All of this information is the FormControl. It tracks the value and validation status of the input. It’s just a class that we create for each field in our form.

We can use the FormControl to get and set the value of the field, find its status, and add validation rules to it. This is done by using the ngModel directive.

To create a FormControl, we instantiate a new FormControl() .

Example:
name = new FormControl();

When we have the object, we can access its value with dot notation and the value property.

Example:
name.value

We can also check the validation status.

Example:
name.valid   // Returns true if the value is valid
name.dirty   // Returns true if the value is dirty
name.touched // Returns true if the field has been touched

name.error   // Returns a list of errors

But what does valid, dirty and touched actually mean.

  • Valid: The value from the field has to pass all validations we set to be marked as Valid.
  • Dirty: When the user changes the value, it’s marked as Dirty.
  • Touched: When the user has interacted with the field or clicked away from it (“blurred”).

We can use the constructor to pass in an initial value and validations.

Example:
name = new FormControl('John Doe', Validators.minLength(3));

In the example above, the initial value will be “John Doe” and must be at least 3 characters long.

FormGroup

A FormGroup is a collection of FormControls where each FormControl is a property.

Forms often have more than one field so it’s helpful to have a simple way to manage the controls together.

As an example, let’s consider the following three inputs.

Example:
City: <input type="text" name="city" />
Suburb: <input type="text" name="suburb" />
Street: <input type="text" name="street" />

Each of the fields in the example above is a separate FormControl.

It would be cumbersome to perform a validation check on each one individually. Even more so when the form has a large number of fields.

The FormGroup solves this issue. It provides a wrapper around all the FormControls we specify and can track the status of child FormControl.

We can specify the individual FormControls in the FormGroup’s constructor.

Example:
address = new FormGroup({
  city: new FormControl(''),
  suburb: new FormControl(''),
  street: new FormControl('')
});

We can also pass initial values and validations in each FormControl.

Example:
fullName = new FormGroup({
  fName: new FormControl('John', Validators.minLength(3)),
  lName: new FormControl('Doe', Validators.minLength(3))
});

Because the FormControls are now in a group, we can perform a validation check on the entire group.

If one control returns an ‘invalid’ status, the whole group will fail the validation check.

If we use the value property to access the control’s values, it will return an object with the all the values of the group.

Example:
fullName.value

// Returns
fullName {
  fName: 'John',
  lName: 'Doe'
}

If we wanted to access the individual control value, we have to use the control’s name as an argument for the get() method on the group’s object.

Example:
fullname.get('fName')

// Returns
'John'

We check the validation status the same way as we do with a single FormControl, but this time on the group’s object.

Example:
fullName.valid   // True if all child control values passed validation
fullName.dirty   // True if one or more child control values have been changed
fullName.touched // True if one or more child control received interaction

fullName.error   // Returns a list of errors

FormArray

A FormArray is similar to a FormGroup.

  • A FormGroup contains each control as a single property.
  • A FormArray contains an array of controls as a single property.

A FormArray is useful for lists, such as date-of-birth or states in a country.

We can define a FormArray inside a FormGroup.

Example:
contactForm = new FormGroup({
  fName: new FormControl(''),
  lName: new FormControl(''),

  state: new FormArray([
    new FormControl('Texas'),
    new FormControl('Florida'),
    new FormControl('Georgia')
  ])
});

Note that we do not have key:value pairs in the array, only values.

You can get the reference to the array, as a FormArray, from the get() method on the group’s object.

Example:
getState(): FormArray {
  return this.contactForm.get('state') as FormArray;
}

Just like FormControl and FormGroup, we can check the validation status on the array.

Example:
state.valid   // True if all child control values passed validation
state.dirty   // True if one or more child control values have been changed
state.touched // True if one or more child control received interaction

state.error   // Returns a list of errors

Reactive Form example

Let’s see a working example of everything we learned above.

1. To be able to work with Reactive Forms, we must import the ReactiveFormsModule from ‘@angular/forms’.

We also need to add it to the ‘imports’ array so that it’s available within the App module.

Example: app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent  } from './app.component';

import { ReactiveFormsModule } from '@angular/forms';

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

2. Next, we need to create our form model in the component class.

As mentioned before, we don’t need to setup any components. We will be working in the main ‘app’ component for the demonstration.

Example: app.component.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {

  contactForm = new FormGroup({
    fName: new FormControl(),
    lName: new FormControl()
  });
}

In the example above we import FormGroup and FormControl from ‘@angular/forms’, and then create a simple group with two controls.

3. Next, we need to create the actual form in the component’s view with fields that correspond to the FormControls.

Example: app.component.html
<form [formGroup]="contactForm" (ngSubmit)="onSubmit()">

  <p>
    <label>First name: </label>
    <input type="text" name="fname" formControlName="fName">
  </p>
  <p>
    <label>Last name: </label>
    <input type="text" name="lname" formControlName="lName">
  </p>

  <button type="submit">Submit</button>

</form>

In the example above, we do three things.

  • First, we associate our model with the form by using the formGroup directive with the group’s name as the value.
  • Next, we tell Angular what we want to happen when the user submits the form with the ngSubmit directive. In this case we want to execute a method called onSubmit() that doesn’t exist yet.
  • Next, we link our fields to their corresponding controls with the formControlName attribute, specifying the control property name as the value.

4. Now we need to create the onSubmit() method that will process the form.

Example: app.component.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {

  contactForm = new FormGroup({
    fName: new FormControl(),
    lName: new FormControl()
  });

  onSubmit() {
    console.log(this.contactForm.value);
  }
}

To keep the demonstration simple, the method will just log the form values to the console as an object.

If we run the app and submit some dummy data, it will show in the browser’s console.

Output: Console
{fName: "John", lName: "Doe"}
  fName: "John"
  lName: "Doe"

Validators

As we’ve seen from the FormControl section above, adding validation to our FormControls is easy.

We import the Validators module from ‘@angular/forms’ and add the validator we want as the second argument to the FormControl constructor.

If a FormControl has more than one validator, we specify them in an array with square brackets.

Example: app.component.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {

  contactForm = new FormGroup({
    fName: new FormControl('', Validators.required),
    lName: new FormControl('', [Validators.required, Validators.minLength(3)])
  });

  onSubmit() {
    console.log(this.contactForm.value);
  }
}

The example above specifies that both fields are required, and the last name must be at least 3 characters long.

Example: app.component.html
<form [formGroup]="contactForm" (ngSubmit)="onSubmit()">

  <p>
    <label>First name: </label>
    <input type="text" name="fname" formControlName="fName">
  </p>

  <p>
    <label>Last name: </label>
    <input type="text" name="lname" formControlName="lName">
  </p>

  <button type="submit" [disabled]="!contactForm.valid">Submit</button>

</form>

In the example above we disable the Submit button if the validation on the form fails.

The official Angular documentation has a complete list of all form Validators .

Reset

We can use the FormGroup’s built-in reset() method to reset all the controls of a group to untouched and pristine.

Example: app.component.html
<form [formGroup]="contactForm" (ngSubmit)="onSubmit()">

  <p>
    <label>First name: </label>
    <input type="text" name="fname" formControlName="fName">
  </p>

  <p>
    <label>Last name: </label>
    <input type="text" name="lname" formControlName="lName">
  </p>

  <button type="submit" [disabled]="!contactForm.valid">Submit</button>
  <button (click)="onReset()">Reset</button>

</form>

In the example above we add a button that will fire the onReset() method when the user clicks on it.

Example: app.component.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {

  contactForm = new FormGroup({
    fName: new FormControl('', Validators.required),
    lName: new FormControl('', [Validators.required, Validators.minLength(3)])
  });

  onSubmit() {
    console.log(this.contactForm.value);
  }

  onReset() {
    this.contactForm.reset();
  }
}

The onReset() method simply invokes the reset() method from the FormGroup API.

If we run the app and add some dummy data, then click the Reset button, the form will be cleared.