Angular 2 Forms

Jesse Sanders
BrieBug
CEO, Application Architect

www.briebug.com
CEO, BrieBug - www.briebug.com -7 years
Developer for 20 years
- Mapquest
- Arrow Electronics
- Starz
- Pearson eCollege
- CenterStone Tech/DV Mobile
- Hawaiian Airlines/BlndSpt
- Pensco

www.briebug.com
We will cover
- Form Validation
- template driven
- model driven
- Custom Validators
- Q & A

www.briebug.com
Template Driven Form Validation
Pros
- Just like Angular 1.x (mostly)
- HTML attribute based validation
- directives like required and minlength
- Easy to use
Cons
- HTML has business rules in it
- Challenging to make it conditional

www.briebug.com
Template Driven Form Validation
<form #registrationForm="ngForm" id="registrationForm" name=" (ngSubmit)="registerUser(registrationForm, email)" novalidate>
<div class="form-group">
<label for="username">Username</label>
<input id="username" name="username" type="text" class="form-control" [(ngModel)]="user. #userName="ngModel" placeholder="Username" required minlength="5">
</div>
<div class="form-group">
<label for="email">Email</label>
<input id="email" name="email" type="text" class="form-control" [(ngModel)]="user.email" #email=" placeholder="Email" required validateEmail>
</div>
<div class="form-group">
<label for="password">Password</label>
<input id="password" name="password" type="password" class="form-control" [(ngModel)]="user. #password="ngModel" placeholder="Password" required>
</div>
<div class="form-group">
<label for="comparePassword">Compare Password</label>
<input id="comparePassword" name="comparePassword" type="comparePassword" class="form-control [(ngModel)]="user. #comparePassword="ngModel" placeholder="Compare Password" required validateEqual="password" reverse="true">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Register</button>
</div>
</form>

www.briebug.com
Template Driven Form Validation
- Create a form
- use novalidate
- use # notation to create a local form variable
- Create controls
- ngModel is required for validators to work
- Banana box if you want two way binding
- [()] (looks like bananas in a box)(if I squint)
- use # notation to create a local variable
- Use local variables to create conditional validation messages
-
<small [hidden]="!c.valid || (c.pristine && !f.submitted)">
-

www.briebug.com
Template Driven Form Validation
Let's look at some actual code...

www.briebug.com
Model Driven Form Validation
Pros
- HTML is independent from business rules
- Easy to create conditional validation
- The component defines how to validate
- HTML can be shared amongst components
- base address class
- have inherited components for mailing and physical address that have different validation rules.

www.briebug.com
Model Driven Form Validation
Cons
- It's different
- A little more setup to add validators
- Lots of old/misinformation from prior Release Candidates < RC4
- Doesn't remove validators automatically when control is hidden or removed.
- Nested components within a form don't automatically wire-up.

www.briebug.com
Model Driven Form Validation
<form id="registrationForm" name="registrationForm" [formGroup]="registrationForm" (ngSubmit)="registerUser(registrationForm)" novalidate>
<div class="form-group">
<label for="username">Username</label>
<input id="username" name="username" type="text" class="form-control" [(ngModel)]="user.userName" placeholder="Username" formControlName="username">
</div>
<div class="form-group">
<label for="email">Email</label>
<input id="email" name="email" type="text" class="form-control" [(ngModel)]="user.email" placeholder="Email" formControlName="email">
</div>
<div class="form-group">
<label for="password">Password</label>
<input id="password" name="password" type="password" class="form-control" [(ngModel)]="user.password" placeholder="Password" formControlName="password">
</div>
<div class="form-group">
<label for="comparePassword">Compare Password</label>
<input id="comparePassword" name="comparePassword" type="comparePassword" class="form-control" [(ngModel)]="user.comparePassword" placeholder="Compare Password" formControlName="comparePassword">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Register</button>
</div>
</form>

www.briebug.com
Model Driven Form Validation
- Create a form
- novalidate
- [formGroup]="registrationForm"
- Create controls
- ngModel is required for validators to work
- formControlName directive
- Use local variables to create conditional validation messages
- Use Form Groups

www.briebug.com
Model Driven Form Validation
What are Form Groups?
Form groups are grouping structures within a form and comprised of FormControls andChild Form Groups
Responsible for managing form controls and collating the validation status of the child controls.
let personInfo = new FormGroup({
firstName: new FormControl("Nate")
})

www.briebug.com
Model Driven Form Validation
What are Form Controls?
Form controls represent a single input control and encapsulates field's value, states, errors, valid, etc.
To attach an html input field to a FormControl, we use the formControlName directive:
<input id="username" name="username" type="text" class="form-control" [(ngModel)]="user.userName" placeholder="Username" formControlName="username">

www.briebug.com
Model Driven Form Validation
Import what?
import {REACTIVE_FORM_DIRECTIVES, FormBuilder, FormGroup} from '@angular/forms';
directives: [REACTIVE_FORM_DIRECTIVES]
constructor(private fb: FormBuilder) { }

www.briebug.com
Model Driven Form Validation
Create Model Validation
// create registrationForm validation model
this.registrationForm = this.fb.group({
// format is key: ['{defaultValue}', [validators])
username: ['', Validators.compose([Validators.required, Validators.minLength(5)])],
email: ['', [Validators.required, NPValidators.email]],
password: ['', Validators.required],
comparePassword: ['', [Validators.required, NPValidators.compareEqual('password', true)]]
});

www.briebug.com
Model Driven Form Validation
Validators.compose
Pro:
strongly enforces your custom validators
Con:
strongly enforces your custom validators

www.briebug.com
Model Driven Form Validation
Let's look at some code

www.briebug.com
Custom Validators
Angular 2 Validation supports:
- required
- minlength
- maxlength
- pattern
What if I need something custom? How do I create my own validators?

www.briebug.com
Custom Validation
Directives vs Classes?
Historically, we always had to create a directive for custom validators. Now we have a choice.
- Directives are required for template driven validation
- Classes work best for model driven validation
Best Practice? Support both.

www.briebug.com
Custom Validators
export class Validators {
/**
* Validator that requires controls to have a non-empty value.
*/
static required(control: AbstractControl): {[key: string]: boolean} {
return isBlank(control.value) || (isString(control.value) && control.value == '') ?
{'required': true} :
null;
}
}
Validation Class Example
https://github.com/angular/angular/blob/master/modules/%40angular/forms/src/validators.ts

www.briebug.com
Custom Validators
Email Validator Class
import {AbstractControl} from '@angular/forms';
export class BBValidators {
static email(control: AbstractControl): {[key: string]: boolean} {
/* tslint:disable:max-line-length */
let EMAIL_REGEXP = /^(([^<>()\[\]\\.,;:\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,}))$/i;
if (!control.value) {
return null;
}
return EMAIL_REGEXP.test(control.value) ?
null : {'email': true};
}
}

www.briebug.com
Custom Validators
Email Validator Directive
import {Directive} from '@angular/core';
import {NG_VALIDATORS} from '@angular/forms';
import {BBValidators} from '../validators/bb-validators';
@Directive({
/* tslint:disable:directive-selector-name */
selector: '[validateEmail][ngModel],[validateEmail][formControl],[validateEmail][formControlName]',
providers: [
{
provide: NG_VALIDATORS,
useValue: BBValidators.email,
multi: true
}
]
})
export class EmailValidatorDirective {}

www.briebug.com
Custom Validators
How to use in a template?
import {EmailValidatorDirective} from '../../directives/emailValidator.directive';
directives: [EmailValidatorDirective]
<input id="email" name="email" type="text" class="form-control [(ngModel)]="user.email" #email="ngModel"placeholder="Email" required validateEmail>

www.briebug.com
Custom Validators
How to use in a model?
import {NPValidators} from '../../validators/np-validators';
this.registrationForm = this.fb.group({
email: ['', [Validators.required, NPValidators.email]]
});
<input id="email" name="email" type="text" class="form-control" [(ngModel)]="user.email" placeholder="Email" formControlName="email">

www.briebug.com
Model Validation Challenges?
How could this possibly happen?
Remember the Cons?
Validators don't automatically remove themselves when hidden or removed.
Nested components within a form don't automatically wire up.

www.briebug.com
Validators don't automatically remove themselves when hidden or removed.
import {Directive, Host, OnDestroy, Input, OnInit} from '@angular/core';
import {ControlContainer} from '@angular/forms';
@Directive({
selector: '[activeControl]'
})
export class ActiveControlControlDirective implements OnInit, OnDestroy {
@Input() npControl: string;
constructor(@Host() private _parent: ControlContainer) {
}
ngOnInit(): void {
setTimeout(() => this.formGroup.form.include(this.npControl));
}
ngOnDestroy(): void {
this.formGroup.form.exclude(this.npControl);
}
get formGroup(): any {
return this._parent;
}
}

www.briebug.com
Nested components within a form don't automatically wire up.
Solution:
Pass the FormGroup to component and then assign it to a formGroup directive:
<div [formGroup]="form">
<label [id]="name">{{model.name}}</label>
<input [id]="name" [name]="name" type="text" [(ngModel)]="model.name" class="form-control" formControlName="name"
[npControl]="name" />
</div>

www.briebug.com
Angular 2 Forms
Questions?
ng2-forms-presentation
By briebug
ng2-forms-presentation
August 24 2016 presentation slides for form validation
- 1,432