5. Forms and Validation
Angular provides two different approaches to handling user input through forms: reactive and template-driven.
1. Template-based Forms
Template-driven forms rely on directives in the template to create and manipulate the underlying object model. They are useful for adding a simple form to an app but don"t scale very well.
To use template-base forms we need to import FormsModule.
1.1. Defining the form in the HTML code
Usually, we start with defining our HTML code as the template holds most of the form logic
<form #loginForm="ngForm" (ngSubmit)="login(loginForm.value)">
<div class="form-group">
<label for="username">Login:</label>
<input (ngModel)="username" name="username" type="text" id="username" class="form-control">
</div>
<div class="form-group">
<label for="password">Password:</label>
<input (ngModel)="password" name="password" type="password" id="password" class="form-control">
</div>
<button type="submit" class="btn btn-primary"></button>
</form>
⚠️ if we wanted to have initial data of our form (from a service or initial data) we should use two-way binding → [(ngModel)]="variable"
1.2. Wiring it up in the component code
We just need to define how to handle the form validation
export class LoginComponent {
constructor(private authService :AuthService, private router: Router) { }
login(formValues) {
this.authService.loginUser(formValues.username, formValues.password);
this.router.navigate(['events']);
}
}
1.3. Validation of reactive forms
Angular provides a lot of state checks on controls:
validinvaliddirtypristinetoucheduntouched
Using these we can easily create conditionals for displaying error-messages or disabling certain fields of the form
<em *ngIf="loginForm.controls.password?.invalid">Required</em>
...
<button type="submit" [disabled]="loginForm.invalid"></button>
2. Reactive Forms
Reactive Forms or Model-based forms are a little bit more flexible and they're are more scalable, reusable, and testable. They are the preferred option.
To use reactive forms we need to import ReactiveFormsModule.
2.1. Defining the form in the component
The form and its associate controls need to be declare in ou component code
import { Component, OnInit } from '@angular/core'
import { FormControl, FormGroup } from '@angular/forms'
import { AuthService } from './auth.service';
@Component({
templateUrl: './profile.component.html'
})
export class ProfileComponent implements OnInit {
profileForm: FormGroup;
constructor(private authService: AuthService) { }
ngOnInit() {
let firstName = new FormControl(this.authService.currentUser.firstName);
let lastName = new FormControl(this.authService.currentUser.lastName);
this.profileForm = new FormGroup({
firstName: firstName,
lastName: lastName
)};
}
saveProfile(formValues) {
this.authService.updateCurrentUser(formValues.firstName, formValues.lastName);
}
}
We use FormControl for each field of the form and FormGroup to group controls. We can also pre-populate the form with our AuthService
2.2. Wiring it up in the template
<form [formGroup]="profileForm" (ngSubmit)="saveProfile" autocomplete="off" novalidate>
<div class="form-group">
<label for="firstName">First Name:</label>
<input formControlName="firstName" id="firstName" type="text" class="form-control" placeholder="First Name..." />
</div>
<div class="form-group">
<label for="lastName">Last Name:</label>
<input formControlName="lastName" id="lastName" type="text" class="form-control" placeholder="Last Name..." />
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
2.3. Validation of reactive forms
We need to use Validators functions on our form elements. We can use multiple Validators on each field
import { Component, OnInit } from '@angular/core'
import { FormControl, FormGroup } from '@angular/forms'
import { AuthService } from './auth.service';
@Component({
templateUrl: './profile.component.html'
})
export class ProfileComponent implements OnInit {
profileForm: FormGroup;
private firstName: FormControl;
private lastName: FormControl;
constructor(private authService: AuthService) { }
ngOnInit() {
this.firstName = new FormControl(this.authService.currentUser.firstName, [Validators.required, Validators.pattern('[a-zA-Z].*')]);
this.lastName = new FormControl(this.authService.currentUser.lastName, Validators.required);
this.profileForm = new FormGroup({
firstName: firstName,
lastName: lastName
)};
}
validateFirstName() {
return this.firstName.valid || this.firstName.untouched;
}
saveProfile(formValues) {
if (this.profileForm.valid) {
this.authService.updateCurrentUser(formValues.firstName, formValues.lastName);
}
}
}
In our HTML, we could display error messages based on our form elements' states.
<em *ngIf="profileForm.controls.firstName.errors.required">Required</em>
...
<em *ngIf="profileForm.controls.firstName.errors.pattern">Must start with a letter</em>
Creating custom validators
Beyond Angular's validators, we can define our own custom validator method
ngOnInit() {
this.abstract = new FormControl('', [Validators.required, Validators.maxLength(400), this.restrictedWords(['foo', 'bar'])]);
}
private restrictedWords(words) {
return (control: FormContol): {[key: string]: any } => {
if (!words) return null;
let invalidWords = words.map(w => control.value.includes(w) ? w : null).filter(w => w!== null)
return invalidWords && invalidWords.length > 0 : { 'restrictedWords': invalidWords.join(',') };
}
}
⚠️ The key needs to match our function's name.
In our HTML, we could display error messages based on our form elements' states.
<em *ngIf="abstract.errors.restrictedWords">Restricted words found: {{ abstract.errors.restrictedWords }}</em>