20

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.

5
  • You can implement your own validator directive Commented Oct 12, 2018 at 2:47
  • You will need one *ngIf for each single error message. For validator, you can implement one validator returning different values, that you can evaluate. Commented Oct 12, 2018 at 3:11
  • @VithuBati, HDJEMAI It would be great, if you share small code example or some reference link. Commented Oct 12, 2018 at 3:21
  • I'll try to create an example with your minimal code if possible, it may take me some time to answer. Commented Oct 12, 2018 at 3:41
  • @HDJEMAI have you provided some example on how to do so with a validator please Im kinda stuck in the same issue. Commented Jul 16 at 22:18

7 Answers 7

32

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> 
Sign up to request clarification or add additional context in comments.

3 Comments

love the idea, but I'm getting the following error :( ERROR Error: No value accessor for form control with unspecified name attribute
You should use name attribute for your control. Refer stackoverflow.com/questions/46708080/…
This is a nice approach, well how to get the name of the invalid control in print-error component?
11

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> 

Comments

8

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.

Comments

1

Another way to do this is using a *ngFor directive. However, there some problems with this method that need to be provided for:

  1. ValidationErrors is an associative array (an object with 1 or more keys, each associated with some error value) while *ngFor requires an iterable array.
  2. The *ngFor causes a lot of reloads, so the error messages are constantly re-rendered.
  3. You have to choose on some scheme how the error messages look like that your validators ALL conform to, because your message is not hardcoded any more in the HTML template, but are passed in in the various ValidationErrors objects.

Some strategies to solve the above (adapt according to the specifics on your project):

  1. I use a utility method (perhaps a static in a utility class) to convert the ValidationErrors object to an array of entries (like [ { key: msg } ]).
  2. The re-rendering is solved by providing a custom "trackBy" function to the trackBy: property in the *ngFor. Maybe another static in the same utility class.
  3. The easiest is to use a TranslatePipe and have translations for each validator error key. (You could use the associated error data as string interpolation parameters, if set up as an object with key:value pairs.) Another way would be to use the error data associated with the error key to store the final string in it, or a translation pipe key. Or some variation of these, but this will influence how your error message tag looks like. In the example below I chose to use each error key as a translation key, and the error data as an interpolation object.

Putting this into code:

Utility class:

import ... export class MyValidatorUtil { ... public static getErrors(control: AbstractControl | null): ValidationErrors[] { return Object.entries(control?.errors ?? {}) .map(([key, msg]: [string, any]) => ({ key, msg })); } public static errorTrack(index: number, err: ValidationErrors): string { return err['key'] ?? ''; } ... } 

HTML Template:

<input type="text" formControlName="myInput" .../> <div class="alert" *ngFor="let err of MyValidatorUtil.getErrors(myFormGrp.get('myInput')); trackBy:MyValidatorUtil.errorTrack"> {{err['key']}} | translate : {{err['msg']}} </div> 

You will need to put a property in your component's TypeScript to be able to use the static functions in your template:

imports ... export class MyForm { public MyValidatorUtil = MyValidatorUtil; // <-- like this public myFormGrp: FormGroup ... 

Comments

0

If it's a small form I usually just use lots of *ngIf; however, a custom validator directive as mentioned above might be useful if your application is almost entirely forms in need of validation.

Take a look at the source, to see how the built-in validators are set up. https://github.com/angular/angular/blob/2.0.0-rc.3/modules/%40angular/common/src/forms-deprecated/directives/validators.ts#L104-L124

Here's an example I dug up, but I think it's a little overkill for most use cases. Just write an *ngIf line in the template HTML, instead of a whole new @Attribute...

https://scotch.io/tutorials/how-to-implement-a-custom-validator-directive-confirm-password-in-angular-2

2 Comments

There are lots of fields and I more interested in that, which provides without *ngIf, I can show multiple angular validators error messages.
Even with a validator, you'll have to use either *ngIf or CSS classes to display whichever error messages you choose. You can show as many or as few as you like. Docs are very complete on the subject: angular.io/guide/form-validation
0

Maybe try this awesome package: https://www.npmjs.com/package/ngx-form-validations

This package has a common dictionary for error message depends on error type. Its installation is not complicated at all.

It can manage your whole form, not just a control. If you need some extra expansion feel free to contact me on GitHub.

Furthermore: There is a demo page where you can easily check its operation and there is a demo project too. Contact details can be found in the package.

1 Comment

It seems awesome, but can't be installed on new angular projects due to dependencies mismatch
0

If anyone is looking for a simple solution written in TypeScript, I was inspired by Jason B and Vinay Somawat's approaches to create my own custom component.

import { Component, Input } from '@angular/core'; import { AbstractControl, AbstractControlDirective } from '@angular/forms'; @Component({ selector: 'form-error', template: ` @if (submitted && controlName != null && controlName.invalid ) { @if (controlName.hasError('required')) { <div class="error-div">This field is required</div> } @else if (controlName.hasError('minlength')) { <div class="error-div">This field is too short</div> } @else if (controlName.hasError('maxlength')) { <div class="error-div">This field is too long</div> } @else if (controlName.hasError('email')) { <div class="error-div">Invalid email address</div> } @else if (controlName.hasError('pattern')) { <div class="error-div">Invalid file type</div> } @else { <div class="error-div">There's some other problem</div> } } `, styleUrls: ['./form-error.component.scss'] }) export class FormErrorComponent { @Input() controlName!: AbstractControl | AbstractControlDirective; @Input() submitted!: boolean; } 

There are a couple of differences worth mentioning. First, I'm validating when a form is submitted (via the "submitted" input parameter) not when the form control is dirty or touched. Second, I've only included messages for the five validation errors I've needed, plus a default error message. More can be added as needed.

Here's the CSS for the "error-div" class. I use the :before tag to draw a small error pointing to the input field.

.error-div { margin: 5px 0px 0px 10px; color:red; font-weight: bold; text-align: center; font-size: 15px; padding: 2px 13px 2px 2px; border: 1px solid red; border-radius: 3px; background-color: rgb(255, 192, 192); width: max-content; } .error-div:before { content: ""; position: relative; left: 10px; right: 0px; top: -26px; margin: 0px auto 0px auto; width: 0; height: 0; border-bottom: 5px solid red; border-left: 5px solid transparent; border-right: 5px solid transparent; } 

Finally, here's a sample of how it's used in the form components:

<div class="entry-field"> <label htmlFor="firstName" class="label">First Name <required-marker/></label> <input id="firstName" name="firstName" type="text" class="textbox" formControlName="firstName" /> <form-error [controlName]="entryForm.controls.firstName" [submitted]="submitted" /> </div> 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.