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.