Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to show error messages for angular reactive forms, one formcontrol multiple validation errors?

I am showing reactive form error messages as per the suggested approach of angular angular form validation error example.

html code of showing error on the page:

<div [formGroup]="myForm">
  <div>
<input type="text" formControlName="firstName"/>
<div *ngIf="myForm.controls.firstName.invalid"
    class="alert alert-danger">
    <div *ngIf="myForm.controls.firstName.errors.required">
      This Field is Required.
    </div>
    <div *ngIf="myForm.controls.firstName.errors.maxlength">
      your can enter only 50 characters
    </div>
</div>
  </div>
  <div>
<input type="text" formControlName="lastName"/>
<div *ngIf="myForm.controls.lastName.invalid"
    class="alert alert-danger">
    <div *ngIf="myForm.controls.lastName.errors.required">
      This Field is Required.
    </div>
    <div *ngIf="myForm.controls.lastName.errors.maxlength">
      your can enter only 50 characters
    </div>
</div>
  </div>
  </div>

Just for the reference of my component code below :

this.myForm = this.formBuilder.group({
      firstName:['',[Validators.required,Validators.maxLength(50)]],
      lastName:['',[Validators.required,Validators.maxLength(50)]]
    })

If you see the above code, I have applied two validation on my firstName and lastName field.

For showing error message, I have written multiple *ngIf condition to show the error message.

Is there any best way to show the validation message of particular control without writing multiple *ngIf condition ?, because the same code I am writing again and again with different control name and validator name for showing error message.

like image 618
vinit tyagi Avatar asked Oct 12 '18 02:10

vinit tyagi


3 Answers

I would suggest to have a component called print-error which can handle any kind of OOTB or Custom errors.

You can handle as many as errors you want.

print-error.component.ts

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

@Component({
    selector: 'print-error',
    templateUrl: './print-error.component.html',
    providers: []
})
export class PrintError {

    @Input("control")
    control: any;

}

print-error.component.html

<div class="text-danger" *ngIf="control && control.errors && (control.dirty || control.touched)">
     <div *ngIf="control.errors.required"><small>This field is required</small></div>
     <div *ngIf="control.errors.unique"><small>{{control.errors.unique}}</small></div>
     <div *ngIf="control.errors.lessThen"><small>{{control.errors.lessThen}}</small></div>
     <div *ngIf="control.errors.greaterThan"><small>{{control.errors.greaterThan}}</small></div>
     <div *ngIf="control.errors.email"><small>{{control.errors.email}}</small></div>
     <div *ngIf="control.errors.mobile"><small>{{control.errors.mobile}}</small></div>
     <div *ngIf="control.errors.confirmPassword"><small>{{control.errors.confirmPassword}}</small></div>
</div>

Usages

 <label for="folder-name">Email</label>
 <input name="email" required   emailValidator #email="ngModel" [(ngModel)]="user.email">
 <print-error [control]="email"></print-error>
like image 140
Sunil Singh Avatar answered Oct 16 '22 12:10

Sunil Singh


A better way to handle all the error, Create a separate component error-component

error.component.ts

import { Component, Input } from '@angular/core';
import { AbstractControl, AbstractControlDirective } from '@angular/forms';

@Component({
    selector: 'error-component',
    templateUrl: 'error.component.html',
    styleUrls: ['error.component.scss']
})

export class ErrorComponent {

    errorMsgList: any = [];

    @Input() controlName: AbstractControl | AbstractControlDirective

    errorMessage = {
        'required'  : (params)  => `This field is required`,
        'maxlength' : (params)  => `Maximum ${params.requiredLength} characters are allowed`,
        'minlength' : (params)  => `Minimum ${params.requiredLength} characters are required`,
        'pattern'   : (params)  => `Invalid format`,
        'min'       : (params)  => `Minimum amount should be ₹ ${params.min}`,
        'whitespace': (params)   => `White spaces are not allowed`
    };


    listErrors() {
        if (!this.controlName) return [];
        if (this.controlName.errors) {
            this.errorMsgList = [];
            Object.keys(this.controlName.errors).map( error => {
                this.controlName.touched || this.controlName.dirty ?
                this.errorMsgList.push(this.errorMessage[error](this.controlName.errors[error])) : '';
            });
            return this.errorMsgList;
        }
        else {
            return [];
        }
    }
}

error.component.html

<small class="error-block" *ngFor="let errorMessage of listErrors(); let last=last;">
    {{last ? errorMessage: ''}}
</small>  

Usages

<input 
   [type] ="inputObj.mobileNumber.type" 
   id="id1" name="custMobNumber" 
   [(ngModel)]="inputObj.mobileNumber.value" 
   [required]="inputObj.mobileNumber.required" 
   [minlength]="inputObj.mobileNumber.minLength" 
   [maxlength]="inputObj.mobileNumber.maxLength" 
   [pattern]="inputObj.mobileNumber.pattern" 
   class="textbox font-15 full-width">
   <error-component [controlName]="collectionForm.controls['custMobNumber']">
    </error-component>
like image 27
Vinay Somawat Avatar answered Oct 16 '22 12:10

Vinay Somawat


I've been working on an enterprise application that is primary form driven and ran into the same challenge. The best solution I could determine was wrapping all of my input controls in components. Then handling the validation display within the component. This allows consistent validation display without repeating the code multiple times in each form.

field-input-text.component.html

    <input [formControl]="formControlItem" [maxlength]="maxlength" [placeholder]="placeholder" #input>
    <span *ngIf="formControlItem.invalid && (formControlItem.dirty || formControlItem.touched)" class="text-danger">
        <span *ngIf="formControlItem.errors.required">This field is required</span>
        <span *ngIf="formControlItem.errors.minlength">This field is too short</span>
        <span *ngIf="formControlItem.errors.maxlength">This field is too long</span>
        <span *ngIf="formControlItem.errors.pattern">Invalid value for this field</span>
    </span>

field-input-text-component.ts

    import { Component, OnInit } from '@angular/core';
    import { FormControl } from '@angular/forms';

    @Component({
      selector: 'app-field-input-text',
      templateUrl: './field-input-text.component.html'
    })
    export class FieldInputTextComponent implements OnInit, AfterViewInit {
      @Input() formControlItem: FormControl;
      @Input() maxlength: number;
      @Input() placeholder: string = '';

      constructor() { }

      ngOnInit() {
      }
    }

Usage

    <app-field-input-text [formControlItem]="form.controls.username" maxlength="10"></app-field-input-text>

In the usage, you can see the space it saves without needing the extra validation lines. You can also reformat all of the validation in one place instead of touching every area.

The main disadvantage is not being able to use the formControl or formControlName attributes. I tried creating a custom ControlValueAccessor component but that did not help with the validation display.

I found your question searching to see if anyone else have found a better way. I know this answer is a little late but hopefully it helps.

like image 6
Jason B Avatar answered Oct 16 '22 12:10

Jason B