Tech tutorials Intro to Angular's Reactive Forms
By Insight Editor / 2 Jul 2018 , Updated on 16 May 2019 / Topics: Application development
By Insight Editor / 2 Jul 2018 , Updated on 16 May 2019 / Topics: Application development
Forms are an inevitable part of any front-end development project — they’re the basis of how applications collect data. Angular attempts to simplify the form creation process by providing two approaches to tackling form development: template-driven forms and reactive forms.
What’s the difference? Template-driven forms are managed in the HTML template, whereas reactive forms are managed in the component.
When to use one over the other? The template-driven option 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 projects that require unit testing.
As the title of this article indicates, we’ll be looking at reactive forms. Why reactive forms? I’ve experienced the power of reactive forms on client projects and how relatively simple it is to develop complex forms.
For you to get the most out of this article, I recommend you have some basic Angular 2+ knowledge.
During this step-by-step guide, the technologies we’ll use are TypeScript and Angular’s Command-Line Interface (CLI). (To use the CLI, you’ll need Node/NPM installed, which we’ll cover as we go along.) Although the example form we’re building doesn’t have submit functionality, it highlights the basics of Angular’s reactive forms, including:
To simplify the setup 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:
In the command prompt, navigate to a directory where you’d like your project to live and create the project by entering:
ng new reactive-forms-app
The CLI will install all of 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 on http://localhost:4200/, unless you’ve specified a different port. After you save the changes you make during development, the browser will refresh automatically.
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
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 the imports array. We also need to reference the reactive-form component we just created. Open your favorite code editor and paste in this code:
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 { }
After the CLI generates the component, we’ll plug in the HTML for the form in the reactive-form.comonent.html file:
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> |
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 Angular’s ngOnInit lifecycle hook. I made initializeForm() a method so that 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.
Stores a reference to the form so we can use it throughout the component; this is the same name we used as the FormGroup attribute on the form element.
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:
*ngIf=”!bookTitle.valid && bookTitle.touched”
Give us access to Angular’s built-in validators and allow 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 at 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) ])], … });
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 a little deeper into validations.
Thanks to the FormBuilder, our inputs (FormControls) have access to a variety of methods and properties, specifically valid, touched and hasError():
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”
Just as our input fields have properties and methods, FormBuilder provides 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”
Open the app.component.html file and reference the element/component inside the app.component.html. In the file, you should 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.
When you’re on your own developing an Angular reactive form, remember the following tips to avoid snafus and make development a little easier:
Hopefully, you’ve discovered the value of using reactive forms and will incorporate them in your next Angular project.