Validations in Angular - Using a single component for showing errors

Validation

Validation is an important part of any web robust application. It makes our application more user-friendly and interactive as well. Forms should be validated properly for all the user input fields. It is essential to use validation to avoid saving/sending invalid data in the database. To guide the user, we use proper meaningful error messages.

Angular Forms

Common validators are required, minLength, maxLength. Four states which are used by forms are as follows:
  • Valid - This is true if all the controls in the form are valid. It's the state of the validity of all form-controls.
  • Invalid - It is opposite of valid. True if any form input control is invalid.
  • Pristine - True if no field is modified yet. It conveys about the cleanliness of the form.
  • Dirty - Opposite of pristine. True if some control is modified.
  • Touched - If any field is touched yet or not. True if touched. 
  • Untouched - Opposite of untouched. 

Template Driven Forms

In Angular, we have different modules for different types of forms. We need to import a specific module explicitly. FormsModule is the module for template-driven forms. Import different modules for template-driven forms.
  • BrowserModule - It provides various services which are required to launch and run a browser application.
  • FormsModule - To use template-driven forms.
  • AppComponent - This is the root component where we implement the form.
import {FormsModule} from '@angular/forms'
import {NgModule} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import {AppComponent} from 'src/app.component';

@NgModule({
  imports: [ BrowserModule, FormsModule ],
  declarations: [ AppComponent],
  bootstrap: [ AppComponent ]
})
export class AppModule {}

Form Validation
When the form module is imported, Angular automatically detects an HTML form element and attached the NgForm component to that element. By adding the NgModel directive, all inputs are registered to NgForm component. 
We can use ngForm with the form element in the following way:
<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)">
....
</form>

Example of a form in Angular
    <form>
      <div>
      <label class="req">Name</label>
      <input [(ngModel)]="name" #nameModel="ngModel" name="name" 
        [pattern]="".*[a-zA-Z].*"" minlength="2" maxlength="80" required autofocus />
         </div>
      <div>
        <label>Birth Year</label> 
        <input [(ngModel)]="birthYear" #birthYearModel="ngModel" name="birthYear"  
         required /></div>
        <div>
        <label>City</label>
        <input [(ngModel)]="city" #cityModel="ngModel" name="city" 
        maxlength="80" required/></div>
      <button type="submit">Save</button>
    </form>
    Now we have to show errors for the above-mentioned input fields. 
    • The name is a required field. It should be more than 2 characters. The maximum length for this is 80. The pattern is also defined for this input.
    • The birth year is a required field.
    • The city is a mandatory field with a maximum length of 80.
    We can implement a helper component Form.error.component.ts to show errors of different input fields as follows:
    
    import { Component, OnInit, Input } from '@angular/core';
    import { AbstractControlDirective, AbstractControl } from '@angular/forms';
    @Component({
      selector: 'app-form-errors',
      template: `
       <ul *ngIf="shouldShowErrors()">
         <li style="color: red" *ngFor="let error of listOfErrors()">{{error}}</li>
       </ul>
     `,
      styleUrls: ['./form-errors.component.scss']
    })
    export class FormErrorsComponent implements OnInit {
      private static readonly errorMessages = {
        'required': () => 'This field is required. ',
        'minlength': (params) => 'Minimum ' + params.requiredLength + 
    ' characters required. ',
        'maxlength': (params) => 'Should not exceed ' + params.requiredLength +
    ' characters long. ',
        'pattern': (params) => (typeof params == "object" ? params.patternErrMsg : null) 
    ||'Invalid input. ',
      };
      @Input() public control: AbstractControlDirective | AbstractControl;
      @Input('patternErrMsg') patternErrMsg: string;
      constructor() { }
      shouldShowErrors(): boolean {
        return this.control &&
          this.control.errors &&
          (this.control.dirty || this.control.touched);
      }
    
      listOfErrors(): string[] {
        return Object.keys(this.control.errors)
          .map(field => this.getMessage(field, this.control.errors[field]));
      }
    
      private getMessage(type: string, params: any) {
        if (!params) {
          params = {};
        }
        if (this.patternErrMsg && typeof params == 'object') {
          params.patternErrMsg = this.patternErrMsg;
        }
        if (FormErrorsComponent.errorMessages[type]) {
          return FormErrorsComponent.errorMessages[type](params);
        } 
       }
    
      ngOnInit() {
      }
    }
    
    
    The component can be used with our input field as follows:
    <div>
      <label class="req">Name</label>
      <input [(ngModel)]="name" #nameModel="ngModel" name="name" 
        [pattern]=".*[a-zA-Z].*" minlength="2" maxlength="80" required/>
    <app-form-errors [control]="nameMode" [patternErrMsg]="'At least one alphabet required.'">
    </div>
    
    

    Comments