Reactive Forms

What Are Reactive Forms?

Reactive forms use a model driven approach to handling form inputs. It uses an explicit and immutable approach where each form change returns a new state. They are built around observable streams where the form inputs and values are provided as streams and gives synchronous access to values.

Reactive Forms Architecture

  • FormGroup - represents the form

    • value - property returns all values in the form

    • valid, invalid, touched, dirty - properties to determine the state of the form

    • controls - returns the list of controls in the form

  • FormControl - represents an element in a form

    • value - property returns the value of the control

    • valid, invalid, touched, dirty - properties to determine the state of the control

Reactive Forms Module

Reactive Forms and its related directives (FormGroup, FormControl) depends on the ReactiveFormsModule.

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { ProfileEditorComponent } from './profile-editor/profile-editor.component';
@NgModule({
declarations: [AppComponent, ProfileEditorComponent],
imports: [BrowserModule, ReactiveFormsModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}app.module.ts

Creating a Reactive Form

ng g c profileEditor
profile-editor.component.html
<label>
First Name:
<input type="text" [formControl]="firstName">
</label>
<p>
Value: {{ firstName?.value }}
</p>
<p>
<button (click)="updateName()">Update Name</button>
</p>
<p>
Form Control: {{ firstName | json }}
</p>

Creating a FormGroup

We see that we can add the FormControl directive to an element, see the validation state change, and update the value. In part 2, we are going to create a FormGroup and expand on our Reactive Form.

profile-editor.component.html
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
<label>
First Name:
<input type="text" formControlName="firstName">
</label>
<label>
Last Name:
<input type="text" formControlName="lastName">
</label>
<button type="submit" [disabled]="!profileForm.valid">Submit</button>
</form>

Notice we are passing an empty string to the FormControl. This is a default value to set the control with.

[FormControl] vs formControlName - [formControl] assigns a reference to the FormControlinstance in the component.

formControlName assigns a string for the FormGroup to look up the control by name.

Validation? Notice that the submit button will be disabled if the form isn't valid. We haven't assigned any validators, so it's always valid. We will assign validators in the next step.

Adding Validators

FormGroup and FormControls make it easy to bind our data to our forms, but we will want to be able to validate that the data conforms to our business rules. In this step, we are going to add validators to our FormGroup. Angular has a few built-in validators:

  • required - a value is required

  • minLength - requires a minimum length

  • maxLength - sets a maximum length

  • pattern - apply a reg expression to determine if field is value

  • min - sets a minimum value allowed (number)

  • max - sets a maximum value allowed (number)

FormBuilder - Notice that we are injecting FormBuilder on the constructor? This utility makes it easier to create FormGroups and FormControls. The key/value notation allows us to set the default value and then one or more validators.

profile-editor.component.html
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
<label>
First Name:
<input type="text" formControlName="firstName">
</label>
<div class="error" *ngIf="showValidation && profileForm.controls.firstName.invalid">Firstname is required.</div>
<br>
<label>
Last Name:
<input type="text" formControlName="lastName">
</label>
<div class="error" *ngIf="showValidation && profileForm.controls.lastName.invalid">Lastname is required.</div>
<br>
<button type="submit">Submit</button>
<p [ngClass]="{'error' : !profileForm.valid}">{{validationMessage}}</p>
</form>

The validation is improved by using a local showValidation variable that doesn't get activated until the submit button has been clicked. Now our form can be submitted, even if not valid, and we can get the user immediate feedback on what is wrong.

Nesting FormGroups

In some scenarios, we will want to be able to group our validation into logical groups. FormGroups support nesting FormGroups and FormControls to create more complex forms.

profile-editor.component.html
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
<label>
First Name:
<input type="text" formControlName="firstName">
</label>
<div class="error" *ngIf="showValidation && profileForm.controls.firstName.invalid">Firstname is required.</div>
<br>
<label>
Last Name:
<input type="text" formControlName="lastName">
</label>
<div class="error" *ngIf="showValidation && profileForm.controls.lastName.invalid">Lastname is required.</div>
<br>
<div formGroupName="address">
<h3>Address</h3>
<label>
Address 1:
<input type="text" formControlName="address1">
</label>
<div class="error" *ngIf="showValidation && profileForm.controls.address.controls.address1.invalid">Address 1 is required and must be at least 5 characters long.</div>
<br>
<label>
City:
<input type="text" formControlName="city">
</label>
<div class="error" *ngIf="showValidation && profileForm.controls.address.controls.city.invalid">City is required.</div>
<br>
<label>
State:
<input type="text" formControlName="state">
</label>
<div class="error" *ngIf="showValidation && profileForm.controls.address.controls.state.invalid">State is required.</div>
<br>
<label>
Zip Code:
<input type="text" formControlName="zip">
</label>
<div class="error" *ngIf="showValidation && profileForm.controls.address.controls.zip.invalid">Zip is required.</div>
<br>
</div>
<button type="submit">Submit</button>
<p [ngClass]="{'error' : !profileForm.valid}">{{validationMessage}}</p>
</form>

Updating Form Values

Sometimes we will want to update the values on our FormGroup with new values. FormGroup has a couple of methods to do so:

  • setValues - sets all values in one shot

  • patchValues - updates only the fields provided

  • setValue - updates a single value

When passing data to a child form component via an input attribute, the FormGroup will be created before the input gets the data. Simply use patchValues within the ngOnChanges lifecycle hook to set the form with the values.

ngOnChanges(changes: SimpleChanges) {

if (changes.data.currentValue) {

this.myForm.patchValues(data);

}

}

Creating a Custom Validator

Custom validators are easy to write and reuse in out applications. These validators can encapsulate complex validation rules.

Summary

  • What are Reactive Forms?

  • Overall architecture

  • Adding ReactiveFormsModule

  • Creating simple ReactiveForms

  • FormGroup, FormControl, and FormBuilder

  • Adding validators and creating custom validators

  • Updating form values