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,335