loading...

Gentle Introduction to Reactive Forms in Angular

abelagoi profile image Agoi Abel Adeyemi ・5 min read

Reactive forms are forms where we write logic, validations, controls in the components class part of the code unlike the template driven forms where control is done in the template. The reactive form is flexible and can be use to handle any complex form scenarios. We write more component code and less html code which make unit testing easier.

Enabling Reactive Forms

To use reactive form, we need to explicitly import {FormsModule, ReactiveFormsModule}in our application module from @angular/forms

import { ReactiveFormsModule } from '@angular/forms';
@Component({...})
export class App { }
@NgModule({ 
    declarations: [App], 
    imports: [BrowserModule, ReactiveFormsModule], 
    bootstrap: [App]
})
export class AppModule {}
platformBrowserDynamic().bootstrapModule(AppModule);

FormControl and FormGroup

You need to understand FormControl and FormGroup to be able to create forms in angular, hence we will talk about the FormControl and FormGroup below.

The FormControl is a class that powers an individual form control, tracks the value and validation status, whilst offering a wide set of public API methods. Below is a basic example of a FormControl

'firstname': new FormControl('');

The FormGroup is a group of FormControl instances, keeps track of the value and validation status for the said group, and also offers public APIs. Below is a basic example of theFormGroup

this.myGroup = new FormGroup({ 
    'firstname': new FormControl(''), 
    'password': new FormControl('')
});

The above has shown us how to create an instance of FormControl and a FormGroup, we can use them within our template like below:

<form [formGroup]="myGroup"> 
    Firstname: <input type="text" formControlName="firstname"> 
    Password: <input type="password" formControlName="password">
</form>

Notice the <form [formGroup]="myGroup">, the formGroup must be equal to the name used to initialise the FormGroup within the component which is myGroup. The formControlName must also be equal to the name used to initialise the FormControl in the component class. Below is the code structure in app.component.ts.

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

@Component({ 
    selector: 'app-root', 
    templateUrl: './app.component.html', 
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit { 
    title = 'reactive forms'; myGroup: FormGroup;
    constructor() {}

    ngOnInit() { 
        this.myGroup = new FormGroup({ 
            'firstname': new FormControl(''), 
            'password': new FormControl('') }); 
        }
    }

The template code below

<form [formGroup]="myGroup"> 
    <div class="form-group"> 
        <label for="firstname" class="label-control">Firstname:</label> 
        <input type="text" formControlName="firstname" class="form-control">        </div>

    <div class="form-group">
        <label for="password" class="label-control">Password:</label> 
        <input type="password" formControlName="password" class="form-control">     </div>
</form>

Nested FormGroups

At times, when building a complex form. Need might arise were we need make a particular value a parent to some other inputs, so we can access those inputs under the parent. In the example below, account will be a parent to firstname and password which means we will be able to access firstname and password under account.

@Component({...})
export class SignupFormComponent { 
    form = new FormGroup({ 
        'email': new FormControl(''), 
        'account': new FormGroup({ 
            'firstname': new FromControl(''), 
            'password': new FromControl(''), 
        })
    });
}

We can represent the nested form group within our template like below

<form [formGroup]="form"> 
    <div formGroupName="account">
        <div class="form-group"> 
            <label for="firstname">Firstname: </label> 
            <input formControlName="firstname" id="firstname" 
                type="text" class="form-control" > 
        </div>

        <div class="form-group"> 
            <label for="password">Password: </label> 
            <input formControlName="password" id="password" 
                    type="password" class="form-control" > 
        </div>
    </div>
</form>

Notice formGroupName is equal to account which is the name assigned to the FormGroup instance within our component. The formGroupName act as a parent to the firstname and password form control.

Simplify with FormBuilder

Instead of using FormGroup and FormControl directly, we can use a magical API that does it all for us. So there will be no need to import FormGroup and FormControl, all we just need import is the FormBuilder

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder } from "@angular/forms";

@Component({ 
    selector: 'app-root', 
    templateUrl: './app.component.html', 
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit { 
    title = 'reactive forms'; 
    myGroup: FormGroup;

    constructor(private formBuilder: FormBuilder) {}

    ngOnInit() { 
        this.myGroup = this.formBuilder.group({ 
            'firstname': this.formBuilder.control(''), 
            'password': this.formBuilder.control('') 
        }); 
    }
}

Reactive Submit

To get the data when the user submit the form, we are going to apply the (ngSubmit)="handleSubmit()" to the <form>which will link to a method in our component to handle the submitted data

<form [formGroup]="myGroup" (ngSubmit)="handleSubmit()">
    <div class="form-group"> 
        <label for="firstname" class="label-control">Firstname:</label 
        <input type="text" formControlName="firstname" class="form-control">        </div>

    <div class="form-group"> 
        <label for="password" class="label-control">Password:</label> 
        <input type="password" formControlName="password" class="form-control">     </div>

    <button class="btn btn-primary">submit</button>
</form>

We can access the form data within our component like below:

handleSubmit() { 
    console.log(this.form.value); 
}

Adding Validation

To add validation to a reactive form, we need to import the Validators class from @angular/forms and pass the validation(s)in as a second argument to our FormControl instances.

import { FormBuilder, Validators } from '@angular/form';
@Component({...})
export class SignupFormComponent implement OnInit {

    constructor(private formBuilder: FormBuilder) {}

    ngOnInit() { 
        this.myGroup = this.formBuilder.group({ 
            'firstname': this.formBuilder.control('', Validators.required),                 'password': this.formBuilder.control('', Validators.required) 
        }); 
    }

}

When we need to use more than one validator, we make the second parameter an array like below:

'firstname': ['', [Validators.required, Validators.minLength(2)]]

Displaying Validation Error

We can add validation error message to our template by listening to the errors object in the FormControl. Thanks to typescript getter, which I use to make the template cleaner

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from "@angular/forms";

@Component({ 
    selector: 'app-root', 
    templateUrl: './app.component.html', 
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit { 
    title = 'reactive forms'; 
    myGroup: FormGroup;

    constructor(private formBuilder: FormBuilder) {} 
    ngOnInit() { 
        this.myGroup = this.formBuilder.group({ 
            'firstname': this.formBuilder.control('',Validators.required),                  'password': this.formBuilder.control('', Validators.required) 
        }); 
    }

    get firstname() { 
        return this.myGroup.get('firstname'); //notice this 
    }

    get password() { 
        return this.myGroup.get('password'); //and this too 
    }
}

We can display validation errors like below within our template

<form [formGroup]="myGroup" (ngSubmit)="handleSubmit()"> 
    <div class="form-group"> 
        <label for="firstname" class="label-control">Firstname:</label> 
        <input type="text" formControlName="firstname" class="form-control" >

        <div *ngIf="firstname.invalid && firstname.touched"> 
            <div *ngIf="firstname.errors.required"> Firstname is required </div>         </div> 
    </div>

    <div class="form-group"> 
        <label for="password" class="label-control">Password:</label> 
        <input type="password" formControlName="password" class="form-control" >

        <div *ngIf="password.invalid && password.touched"> 
            <div *ngIf="password.errors.required"> Password is required </div>          </div> 
    </div>

    <button class="btn btn-primary">submit</button>
</form>

Disable Submit Button

we can disable the submit button when the form is not valid by hooking to the form group valid status like below, note that myGroup is the form control.

<button class="btn btn-primary" [disabled]="! myGroup.valid"> submit</button>

There is more to reactive forms like custom validation and asynchronous validation, there are many articles out there to help with them.

I am happy to share this article with you . If you’ve enjoyed this article, do show support by giving a few claps 👏 . Thanks for your time and make sure to follow me or drop your comment below 👇

Discussion

pic
Editor guide