Angular Forms : Reactive and Template Driven Forms
The Ultimate Guide to Forms in Angular.
Forms are probably the most crucial aspect of your web application. While we often get events from clicking on links or moving the mouse, it’s through forms where we get the majority of our rich data input from users.
On the surface, forms seem straightforward: you make an input
tag, the user fills it out, and hits submit. How hard could it be?
It turns out, forms can be very complex. Here’s a few reasons why:
- Form inputs are meant to modify data, both on the page and the server
- Changes often need to be reflected elsewhere on the page
- Users have a lot of leeway in what they enter, so you need to validate values
- The UI needs to clearly state expectations and errors, if any
- Dependent fields can have complex logic
- We want to be able to test our forms, without relying on DOM selectors
Thankfully, Angular has tools to help with all of these things.
FormControl
s encapsulate the inputs in our forms and give us objects to work with themValidator
s give us the ability to validate inputs, any way we’d like- Observers let us watch our form for changes and respond accordingly
In this chapter we’re going to walk through building forms, step by step. We’ll start with some simple forms and build up to more complicated logic.
FormControl
s and FormGroup
s
The two fundamental objects in Angular forms are FormControl
and FormGroup
.
FormControl
A FormControl
represents a single input field - it is the smallest unit of an Angular form.
FormControl
s encapsulate the field’s value, and states such as being valid, dirty (changed), or has errors.
For instance, here’s how we might use a FormControl
in TypeScript:
// create a new FormControl with the value "Prasad"
let nameControl = new FormControl("Prasad");let name = nameControl.value; // -> Prasad// now we can query this control for certain values:
nameControl.errors // -> StringMap<string, any> of errors
nameControl.dirty // -> false
nameControl.valid // -> true
// etc.
To build up forms we create FormControl
s (and groups of FormControl
s) and then attach metadata and logic to them.
Like many things in Angular, we have a class (FormControl
, in this case) that we attach to the DOM with an attribute (formControl
, in this case). For instance, we might have the following in our form:
<!-- part of some bigger form -->
<input type="text" [formControl]="name" />
This will create a new FormControl
object within the context of our form
. We’ll talk more about how that works below.
FormGroup
Most forms have more than one field, so we need a way to manage multiple FormControl
s. If we wanted to check the validity of our form, it’s cumbersome to iterate over an array of FormControl
s and check each FormControl
for validity. FormGroup
s solve this issue by providing a wrapper interface around a collection of FormControl
s.
Here’s how you create a FormGroup
:
let loginForm = new FormGroup({
firstName: new FormControl("Prasad"),
lastName: new FormControl("Pawar"),
zip: new FormControl("422010")
})
FormGroup
and FormControl
have a common ancestor (AbstractControl
). That means we can check the status
or value
of personInfo
just as easily as a single FormControl
:
loginForm.value;
// -> {
// firstName: "Prasad",
// lastName: "Pawar",
// zip: "422010"
// }// now we can query this control group for certain values, which have sensible
// values depending on the children FormControl's values:
loginForm.errors // -> StringMap<string, any> of errors
loginForm.dirty // -> false
loginForm.valid // -> true
// etc.
Notice that when we tried to get the value
from the FormGroup
we received an object with key-value pairs. This is a really handy way to get the full set of values from our form without having to iterate over each FormControl
individually.
Our First Form
There are lots of moving pieces to create a form, and several important ones we haven’t touched on. Let’s jump in to a full example and I’ll explain each piece as we go along.
Here’s a screenshot of the very first form we’re going to build:
Our form is super simple: we have a single input for Email address, Address and Password (with a label) and a submit button.
Let’s turn this form into a Component. If you recall, there are three parts to defining a component:
- Configure the
@Component()
decorator - Create the template
- Implement custom functionality in the component definition class
Let’s take these in turn:
Loading the FormsModule
In order to use the new forms library we need to first make sure we import the forms library in our NgModule
.
There are two ways of using forms in Angular and we’ll talk about them both in this chapter: using FormsModule
or using ReactiveFormsModule
. Since we’ll use both, we’ll import them both into our module. To do this, we do the following in our app.ts
where we bootstrap the app:
// app.module.ts
import {
FormsModule,
ReactiveFormsModule
} from '@angular/forms';// farther down...@NgModule({
declarations: [
AppComponent,
DemoFormSkuComponent,
// ... our declarations here
],
imports: [
BrowserModule,
FormsModule, // <-- add this
ReactiveFormsModule // <-- and this
],
bootstrap: [ AppComponent ]
})
class AppModule {}
This ensures that we’re able to use the form directives in our views. At the risk of jumping ahead, the FormsModule
gives us template driven directives such as:
ngModel
andNgForm
Whereas ReactiveFormsModule
gives us reactive driven directives like
formControl
andngFormGroup
… and several more. We haven’t talked about how to use these directives or what they do, but we will shortly. For now, just know that by importing FormsModule
and ReactiveFormsModule
into our NgModule
means we can use any of the directives in that list in our view template or inject any of their respective providers into our components.
Reactive- vs. template-driven Forms
Angular allows you to define forms in two different ways: “reactive” or “template” driven. You can see a comparison of two ways here. Rather than describe how they’re different, we’re going to show you examples of different ways you can build forms — then you can decide which is right for your application.
Simple SKU Form: @Component Decorator
First, let’s start by creating what’s called a “template driven” form. Starting with our component:
import { Component, OnInit } from '@angular/core';@Component({
selector: 'app-demo-form-sku',
templateUrl: './demo-form-sku.component.html',
Here we define a selector
of app-demo-form-sku
. If you recall, selector
tells Angular what elements this component will bind to. In this case we can use this component by having a app-demo-form-sku
tag like so:
<app-demo-form-sku></app-demo-form-sku>
Simple SKU Form: template
Let’s look at our template:
<form [formGroup]="loginForm" (ngSubmit)="onLogin()"><div class="form-group"><label>Email Address</label><input type="email" formControlName="email" placeholder="Enter Email" required></div><div class="form-group"><label>Address</label><input type="email" formControlName="address" placeholder="Enter Address" required> </div><div class="form-group"><label>Password</label>
<input type="password" placeholder="Enter Password" formControlName="password" required>
</div><button type="submit" >Submit</button>
</form>
Simple SKU Form: Component Definition Class
Now let’s look at our class definition:
export class DemoFormSkuComponent implements OnInit { constructor() { } ngOnInit() {
}loginForm = new FormGroup({
email: new FormControl(''),
address: new FormControl(''),
password: new FormControl('')
});onLogin() {
console.log("login form value", this.loginForm.value)
}
}
If we click ob submit button in our browser, here’s what it looks like:
Adding Validations
Our users aren’t always going to enter data in exactly the right format. If someone enters data in the wrong format, we want to give them feedback and not allow the form to be submitted. For this we use validators.
Validators are provided by the Validators
module and the simplest validator is Validators.required
which simply says that the designated field is required or else the FormControl
will be considered invalid.
To use validators we need to do two things:
- Assign a validator to the
FormControl
object - Check the status of the validator in the view and take action accordingly
To assign a validator to a FormControl
object we simply pass it as the second argument to our FormControl
constructor:
let control = new FormControl('sku', Validators.required);
Or in our case, because we’re using FormGroup
we will use the following syntax:
loginForm = new FormGroup({email: new FormControl('', Validators.required]),address: new FormControl('', Validators.required),password: new FormControl('', Validators.required)});
To check values of email, address and password gets write a get method
get email() { return this.loginForm.get('email') }get password() { return this.loginForm.get('password') }get address() { return this.loginForm.get('address') }
Now add error message into template
<form [formGroup]="loginForm" (ngSubmit)="onLogin()"><div class="form-group"><label>Email Address</label><input type="email" formControlName="email" placeholder="Enter Email" required>
<span style="color:red" *ngIf="email.invalid && email.touched">Email is required</span>
</div><div class="form-group"><label>Address</label><input type="text" formControlName="address" placeholder="Enter Address" required>
<span style="color:red" *ngIf="address.invalid && address.touched">Address is required</span>
</div><div class="form-group"><label>Password</label>
<input type="password" placeholder="Enter Password" formControlName="password" required>
<span style="color:red" *ngIf="password.invalid && password.touched">Password is required</span>
</div><button type="submit" >Submit</button>
</form>
Here’s a screenshot of what our form is going to look like with validations if we kept values blank and click submit:
Wrapping Up
Forms have a lot of moving pieces, but Angular makes it fairly straightforward. Once you get a handle on how to use FormGroup
s, FormControl
s, and Validation
s, it’s pretty easy going from there!