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
FormcontrolErrorsDirective
in 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_FACTORY
inApplicationConfig
using 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_PROVIDER
referencing 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
ErrorMsgParser
and override the methodparse
to 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_PARSER
inApplicationConfig
using 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
, thecreateComponent
method should return aComponentRef
of 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_FACTORY
inApplicationConfig
using 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.