This is a directive that provides an uncomplicated way to display Angular ValidationErrors in Reactive Forms
See It working on this demo.
You can also try it in your browser here.
npm install --save ngx-formcontrol-errors-msgs
- Import
FormcontrolErrorsDirectivein the component (You must import ReactiveFormsModule too)
import { Component } from '@angular/core'; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; import { FormcontrolErrorsDirective } from "ngx-formcontrol-errors-msgs"; @Component({ selector: 'app-form', standalone: true, imports: [FormcontrolErrorsDirective, ReactiveFormsModule], templateUrl: './app-form.component.html', styleUrl: './app-form.component.scss', }) export class AppForm { constructor(private readonly formBuilder: FormBuilder) {} ... }- Create a form as usual for Reactive Forms
form = this.formBuilder.group({ name: ["", [Validators.required, Validators.maxLength(10)]], email: ["", [Validators.required, Validators.email]], });- Place the directive in the template along with FormControlName or FormControl
<form [formGroup]="form"> <div class="form-row"> <label for="name">Name</label> <input id="name" type="text" formControlName="name" ngxFormcontrolErrors /> </div> <div class="form-row"> <label for="email">Email</label> <input id="email" type="email" formControlName="email" ngxFormcontrolErrors /> </div> </form>Note:
By default this module provides the following messages for Angular built-in Validators:
export const Messages: KeyValueObject = { required: "This field is required", min: "The minimun allowed values is {{min}}", max: "The max allowed value is {{max}}", minlength: "The minimun allowed length is {{requiredLength}}", maxlength: "The max allowed length is {{requiredLength}}", email: "Invalid email", pattern: "Invalid pattern", };Where KeyValueObject is custom type defined by:
export type KeyValueObject = { [key: string]: string };Strings enclosed in double brackets, like {{min}}, {{max}}, {{requiredLength}}, are replaced at runtime by the actual validation reference value. Those messages can be overrided or extended by injecting new ones using FORM_ERROR_MESSAGES_PROVIDER in the ApplicationConfig object.
export const appConfig: ApplicationConfig = { providers: [ ... { provide: FORM_ERROR_MESSAGES_PROVIDER, useValue: { // This message will override the default message required: "This is a <b>required</b> field", // This is a message for a custom validator and will extend the default // messages myCustomValidation: "There is an error", }, }, ... ], };HTML tags are allowed.
If the application uses Angular Internationalization, FORM_ERROR_MESSAGES_PROVIDER could be provided using $localize after adding all necessary settings for Angular I18N
export const appConfig: ApplicationConfig = { providers: [ ... { provide: FORM_ERROR_MESSAGES_PROVIDER, useValue: { required: $localize `This field is required`, min: $localize `The minimun allowed values is {{min}}`, max: $localize `The max allowed value is {{max}}`, minlength: $localize `The minimun allowed length is {{requiredLength}}`, maxlength: $localize `The max allowed length is {{requiredLength}}`, email: $localize `Invalid email`, pattern: $localize `Invalid pattern`, }, }, ... ], };If the application uses ngx-translate, the following settings are required:
- Install the message parser service for
ngx-translate
npm install --save ngx-formcontrol-msgs-translate-parser- Provide
ERROR_MSG_COMPONENT_FACTORYinApplicationConfigusing classTranslateErrorMsgComponentFactoryService
export const appConfig: ApplicationConfig = { providers: [ ... { provide: ERROR_MSG_COMPONENT_FACTORY, useClass: TranslateErrorMsgComponentFactoryService, }, ... ], };- Add the messages in the locale file of each language (as usual for
ngx-translate)
English (en.json)
{ ... "FORM_ERROR_MESSAGES": { "REQUIRED": "This field is required", "MIN": "The minimun allowed values is {{min}}", "MAX": "The max allowed value is {{max}}", "MINLENGTH": "The minimun allowed length is {{requiredLength}}", "MAXLENGTH": "The max allowed length is {{requiredLength}}", "EMAIL": "Invalid email", "PATTERN": "Invalid pattern", "CUSTOM": "Ups, something went wrong", ... } ... }Spanish (es.json)
{ ... "FORM_ERROR_MESSAGES": { "REQUIRED": "Este campo es obligatorio", "MIN": "El mínimo valor permitido es {{min}}", "MAX": "El máximo valor permitido es {{max}}", "MINLENGTH": "El mínimo número de caracteres es {{requiredLength}}", "MAXLENGTH": "El máximo número de caracteres es {{requiredLength}}", "EMAIL": "Email inválido", "PATTERN": "Entrada inválida", "CUSTOM": "Ups, Algo salió mal", ... } ... }- Provide
FORM_ERROR_MESSAGES_PROVIDERreferencing the values in the locale files
export const appConfig: ApplicationConfig = { providers: [ ... { provide: FORM_ERROR_MESSAGES_PROVIDER, useValue: { required: "FORM_ERROR_MESSAGES.REQUIRED", min: "FORM_ERROR_MESSAGES.MIN", max: "FORM_ERROR_MESSAGES.MAX", minlength: "FORM_ERROR_MESSAGES.MINLENGTH", maxlength: "FORM_ERROR_MESSAGES.MAXLENGTH", email: "FORM_ERROR_MESSAGES.EMAIL", pattern: "FORM_ERROR_MESSAGES.PATTERN", custom: "FORM_ERROR_MESSAGES.CUSTOM", ... }, }, ... ], };If the application uses I18N methods other than NGX-TRANSLATE or Angular I18N, there are two posible aproaches: service driven, component driven
- Create a class or service that implements
ErrorMsgParserand override the methodparseto return customized translations that could reliy on a custom I18N service
@Injectable({ ... }) export class CustomMsgParserService implements ErrorMsgParser { constructor( private readonly i18nService: CustomI18NService, @Inject(FORM_ERROR_MESSAGES_PROVIDER) private customErrorMessages: KeyValueObject ) {} parse(error: ValidationErrors): string { ... // Develop the logic to translate `customErrorMessages` using `CustomI18NService` ... } }- Provide
ERROR_MSG_PARSERinApplicationConfigusing the custom class created in the previous step.
export const appConfig: ApplicationConfig = { providers: [ ... { provide: ERROR_MSG_PARSER, useClass: CustomMsgParserService, }, ... ], };Select component driven aproach whenever you want to use any existing pipe, like the translate pipe available in ngx-translate, this way you can provide a custom component that uses the already available pipes.
- Create a component class that implements
ErrorMsgComponent
@Component({ ... }) export class CustomErrorMsgComponent implements ErrorMsgComponent { @Input() messages: ErrorMessage[]; ... }ErrorMessage is an interface with two properties:
export interface ErrorMessage { /** * String to be displayed, customized or translated */ message: string; /** * ValidationError content to be replaced in the `message` string */ value?: unknown; }- Create a service that implements
ErrorMessageComponentFactory, thecreateComponentmethod should return aComponentRefof the component created in the previous step
@Injectable({ ... }) export class CustomMsgComponentFactoryService implements ErrorMessageComponentFactory { constructor() {} createComponent(viewContainerRef: ViewContainerRef): ComponentRef<CustomErrorMsgComponent> { return viewContainerRef.createComponent(CustomErrorMsgComponent); } }- Provide
ERROR_MSG_COMPONENT_FACTORYinApplicationConfigusing classCustomMsgComponentFactoryService(created in the previous step)
export const appConfig: ApplicationConfig = { providers: [ ... { provide: ERROR_MSG_COMPONENT_FACTORY, useClass: CustomMsgComponentFactoryService, }, ... ], };This module does not provide any CSS stylesheet or settings, so a custom style must be applied to fit the look and feel of the application.
This directive attaches a ngx-formcontrol-errors component as siblings of the input elements, styles to those components can be applied globally in the styles.scss of the application
:root { --error-color: #ff0000; } ngx-formcontrol-errors { display: block; font-size: 0.75rem; color: var(--error-color); text-align: right; min-height: 1rem; }Styles can also be applied at component level using ng-deep
::ng-deep ngx-formcontrol-errors { display: block; font-size: 0.75rem; color: var(--error-color); text-align: right; min-height: 1rem; }If you create a Custom error component like in Component driven, you have to replace ngx-formcontrol-errors for your custom component selector.