Friday, August 12, 2016

18. Building Forms with Basic Validation

Template driven form

Building a Basic Form

<form>
    <div class="form-group">
        <label for="firstName">First Name</label>
        <input id="firstName" type="text" class="form-control">
    </div>
    <div class="form-group">
        <label for="comment">Comment</label>
        <textarea id="comment" cols="30" rows="10" class="form-control"></textarea>
    </div>
    <button class="btn btn-primary" type="submit">Submit</button>   
</form>

Control and ControlGroup

Now Want to upgrade form into an angular form

Control class represents a single input field, whether value, touched, untouched, dirty, pristine, valid, errors

Control Group represents a group of controls in a form

Form can contain multiple contorl group e.g. shipping address and billing address

Accessing each control group is easier than iterating through each individual controls in the group

Both Control and ControlGroup inherit from base class AbstractControl

Two ways to create control objects. Implicit and Explicit

Explicit has more contorl over validation logic - and unit test them. Comes with downside that need to write more code. Both are fine, depending on our scenario

WE will explore implicit creation.

ngControl

<form>
    <div class="form-group">
        <label for="firstName">First Name</label>
        <!-- implicit creation of Control objects 
            #firstName is temporary variable
        -->
        <input ngControl="firstName" 
            #firstName="ngForm"
            id="firstName" 
            type="text" 
            class="form-control">
    </div>
    <div class="form-group">
        <label for="comment">Comment</label>
        <!-- ngControl and id identifier no relationship-->
        <textarea ngControl="comment" id="comment" cols="30" rows="10" class="form-control"></textarea>
    </div>
    <button class="btn btn-primary" type="submit">Submit</button>    
</form>

Validation Rules
- required
- minlength
- maxlength

<form>
    <div class="form-group">
        <label for="firstName">First Name</label>
        <!-- implicit creation of Control objects 
            #firstName is temporary variable
        -->
        <input 
            ngControl="firstName" 
            #firstName="ngForm"             
            id="firstName" 
            type="text" 
            class="form-control"
            required>
        <!-- display validation error-->
        <div class="alert alert-danger" 
            *ngIf="firstName.touched && !firstName.valid ">
            First name is required
        </div>
    </div>
    <div class="form-group">
        <label for="comment">Comment</label>
        <!-- ngControl and id identifier no relationship-->
        <textarea 
            #comment="ngForm"
            ngControl="comment" 
            id="comment" 
            cols="30" 
            rows="10" 
            class="form-control"
            required>
        </textarea>
        <div *ngIf="comment.touched && !comment.valid" 
            class="alert alert-danger">Comment is required.</div>
    </div>
    <button class="btn btn-primary" type="submit">Submit</button>    
</form>

styles.css

.ng-touched.ng-invalid{
    border: 1px solid red;
}

but how do I know .ng-touched.ng-invalid?

Check Chrome developer tools

Before i fill in the input field

<input class="form-control ng-untouched ng-pristine ng-invalid" id="firstName" ngcontrol="firstName" required="" type="text" ng-reflect-name="firstName">

When input field is touched and empty

<input class="form-control ng-touched ng-dirty ng-invalid" id="firstName" ngcontrol="firstName" required="" type="text" ng-reflect-name="firstName">

When input field is filled

<input class="form-control ng-touched ng-dirty ng-valid" id="firstName" ngcontrol="firstName" required="" type="text" ng-reflect-name="firstName">

Showing Specific Validation Errors

What if an input field need multiple validation?

e.g. input field is not only required but need at least three characters

     <div class="form-group">
        <label for="firstName">First Name</label>
        <!-- implicit creation of Control objects 
            #firstName is temporary variable
        -->
        <input 
            ngControl="firstName" 
            #firstName="ngForm"             
            id="firstName" 
            type="text" 
            class="form-control"
            required
            minlength="3">
        <!-- display validation error-->
        <div *ngIf="firstName.touched && firstName.errors">
            <div class="alert alert-danger" 
                *ngIf="firstName.errors.required">
                First name is required
            </div>
            <div class="alert alert-danger"
                *ngIf="firstName.errors.minlength">
                First name should be minimum 3 characters.
            </div>
        </div>    
    </div>

But should we hardcode '3'?

        <div *ngIf="firstName.touched && firstName.errors">
            <div class="alert alert-danger" 
                *ngIf="firstName.errors.required">
                First name is required
            </div>
            <div class="alert alert-danger"
                *ngIf="firstName.errors.minlength">
                First name should be minimum {{firstName.errors.minlength.requiredLength}} characters.
            </div>

        </div> 

But how do we know its minlength.requiredLength?

{{firstName.errors | json}}

For e.g. 

        <div *ngIf="firstName.touched && firstName.errors">
            {{firstName.errors | json}}
            <div class="alert alert-danger" 
                *ngIf="firstName.errors.required">
                First name is required
            </div>
            <div class="alert alert-danger"
                *ngIf="firstName.errors.minlength">
                First name should be minimum {{firstName.errors.minlength.requiredLength}} characters.
            </div>

        </div> 

output
{ "minlength": { "requiredLength": 3, "actualLength": 1 } }

Therefore {{firstName.errors.minlength.requiredLength}} 

ngForm

ngForm binds entire form to ControlGroup object. 

<form #f="ngForm" (ngSubmit)="onSubmit(f.form)">

contact-form.component.ts

@Component({
  selector: 'contact-form',
  templateUrl: 'app/contact-form.component.html' 
})
export class ContactFormComponent {
    onSubmit(form){
        // call server api
        console.log(form);
    }

}

Disabling Submit Button

<button class="btn btn-primary" type="submit" [disabled]="!f.valid">Submit</button>

Extended form with dropdown

contact-form.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'contact-form',
  templateUrl: 'app/contact-form.component.html' 
})
export class ContactFormComponent {
    frequencies = [
        { id: 1, label: 'Daily' }, 
        { id: 2, label: 'Weekly' },
        { id: 3, label: 'Monthly' }
    ];

    onSubmit(form){
        // call server api
        console.log(form);
    }

}   

Add below just before Submit button.

contact-form.component.html

    <div class="form-group">
        <label for="frequency">Frequency of emails</label>
        <select
            ngControl="frequency" 
            #frequency="ngForm" 
            id="frequency" 
            class="form-control" 
            required>
            <option value=""></option>
            <option *ngFor="#frequency of frequencies" value="{{ frequency.id }}">
                {{ frequency.label }}
            </option>
        </select> 
        <div 
            class="alert alert-danger" 
            *ngIf="frequency.touched && !frequency.valid">
            This field is required.
        </div>
    </div>  

No comments:

Post a Comment