Errors
This guide details the system for dynamically displaying validation error messages for Angular form controls. It's designed to be flexible, allowing developers to control when and how error messages appear, and to customize the messages themselves.
Overview
The system revolves around the LmnDynamicValidatorMessageDirective
, which automatically listens to a form control's status and displays appropriate error messages. It uses an "error state matcher" function to decide if errors should be shown and a configurable token for error message strings. Messages are rendered into a container specified by LmnValidatorMessageContainerDirective
or a custom
.
Core Directives
This section describes the fundamental directives that form the validation message system. These can be used with standard HTML elements or custom components that don't have built-in integration.
LmnDynamicValidatorMessageDirective
This is the primary directive responsible for observing a form control and rendering validation messages.
- Selector:
[lmnDynamicValidatorMessage]
- Purpose: Applied to an element associated with a form control (e.g., an input field wrapper or the input itself). It automatically detects the
and manages the display of error messages.NgControl
Inputs
Input | Alias | Type | Default Value | Description |
---|---|---|---|---|
disabled | lmnDynamicValidatorDisabled | boolean | false | If true , disables the directive, clearing any displayed errors and preventing new ones from showing. |
errorStateMatcher | - | LmnErrorStateMatcher | LmnErrorStateMatchers | LMN_VALIDATION_ERROR_STATE_MATCHER (see below) | A function or an object of functions to determine if an error message (or specific errors) should be displayed. See |
errorsContainer | - |
| (Optional) | The where error messages will be rendered. If not provided, the directive attempts to use one set by setDefaultErrorContainer . |
validateOn | - | LmnValidateOnEvent | 'focusout' | The DOM event on the host element that triggers a validation check. Options: 'blur' , 'change' , ' , 'focus' , 'focusout' . |
errorsCountToDisplay | - | number | 3 | Maximum number of error messages to display simultaneously. Use -1 to display all errors. |
negative | - | boolean | false | If true , applies a "negative" theme to the error messages (e.g., for dark backgrounds). This is passed to the underlying LmnHelperMessageComponent . |
readonly | - | boolean | false | Indicates if the control is in a read-only state. This can be used by errorStateMatcher functions to prevent showing errors on read-only fields. |
customErrorMessages | - | LmnErrorMessages | {} | A map of custom error messages specific to this control instance, potentially overriding global messages. See |
Public Properties
renderedErrors:
A read-only Angular Signal that emits a map of currently rendered errors. The map keys are error names (e.g.,Signal <Map<string, LmnDynamicError>>required
,minlength
), and values are objects containingerrorName
anderrorId
(the DOM ID of the error message component).
Public Methods
setDefaultErrorContainer(container:
Allows a parent component or directive to provide a defaultViewContainerRef ): void
for rendering errors if theViewContainerRef errorsContainer
input is not explicitly set.
Behavior
- Adds/Removes the
data-lmn-has-error
attribute to the host element when errors are active and should be displayed. - Internally uses
LmnHelperMessageComponent
to render each error message. - Listens to form submission and reset events to update error visibility.
Basic Usage (Manual Application)
<input type="text" [formControl]="myControl" lmnDynamicValidatorMessage [errorsContainer]="errorViewContainer" />
<div>
<!-- Error messages will be rendered here -->
<ng-container #errorViewContainer></ng-container>
</div>
LmnValidatorMessageContainerDirective
A utility directive to easily mark a location in the template for rendering error messages.
- Selector:
[lmnValidatorMessageContainer]
- ExportAs:
lmnValidatorMessageContainer
- Purpose: Provides a convenient way to get a
to pass to theViewContainerRef errorsContainer
input of theLmnDynamicValidatorMessageDirective
.
Properties
container:
TheViewContainerRef
of the host element, which can be directly passed to theViewContainerRef errorsContainer
input ofLmnDynamicValidatorMessageDirective
.
Usage (Manual Application)
<!-- Assume myControl is a FormControl instance -->
<div class="form-field">
<label for="myInput">My Input</label>
<input
id="myInput"
type="text"
[formControl]="myControl"
lmnDynamicValidatorMessage
[errorsContainer]="errorsRef.container"
/>
</div>
<div>
<!-- Error messages for 'myInput' will appear here -->
<ng-container lmnValidatorMessageContainer #errorsRef="lmnValidatorMessageContainer"></ng-container>
</div>
Configuration
This section details how to configure the behavior of the dynamic validation message system, including when errors are shown and what messages are displayed.
Error State Matching
Error state matchers determine when an error message should be displayed. This is typically based on the control's state (e.g., dirty, touched) and the form's submission status.
LmnErrorStateMatcher
Type
A function that defines the logic for showing an error.
export type LmnErrorStateMatcher = (
control: AbstractControl | null,
isFormSubmitted: boolean,
readonly: boolean,
) => boolean | Observable<boolean>;
control
: The
being validated.AbstractControl isFormSubmitted
:true
if the parent form has been submitted.readonly
:true
if the control is marked as read-only.- Returns:
boolean
orObservable<boolean>
indicating if errors should be shown.
LmnErrorStateMatchers
Type
An object allowing different matcher functions for different validation error keys, with a required default
matcher.
export type LmnErrorStateMatchers = {
default: LmnErrorStateMatcher; // Fallback matcher
[key: string]: LmnErrorStateMatcher; // Matcher for a specific error (e.g., 'required', 'minlength')
};
Example of defining custom matchers:
import { onDirtyOrSubmittedErrorStateMatcher, onTouchedOrSubmittedErrorStateMatcher } from '@elemental/ui';
const customMatchersObject: LmnErrorStateMatchers = {
default: onDirtyOrSubmittedErrorStateMatcher, // For most errors
required: (control, isFormSubmitted, readonly) => {
// Special logic for 'required'
return Boolean(control?.invalid && !readonly && (control.touched || isFormSubmitted));
},
};
Using with LmnDynamicValidatorMessageDirective
:
<input [formControl]="myControl" lmnDynamicValidatorMessage [errorStateMatcher]="customMatchersObject" />
Pre-built Matcher Functions
Several common matcher functions are provided:
onDirtyOrSubmittedErrorStateMatcher
: Shows errors if the control is invalid AND (control.dirty
OR form submitted) AND NOTreadonly
.onTouchedOrSubmittedErrorStateMatcher
: Shows errors if the control is invalid AND (control.touched
OR form submitted) AND NOTreadonly
.onTouchedAndDirtyOrSubmittedErrorStateMatcher
: (Default for the system viaLMN_VALIDATION_ERROR_STATE_MATCHER
token) Shows errors if the control is invalid AND ((control.touched
ANDcontrol.dirty
) OR form submitted) AND NOTreadonly
.
LMN_VALIDATION_ERROR_STATE_MATCHER
Token
This
provides the default LmnErrorStateMatcher
function used by LmnDynamicValidatorMessageDirective
if no errorStateMatcher
input is provided.
- Default Factory: Returns
onTouchedAndDirtyOrSubmittedErrorStateMatcher
. - Provided in:
root
(meaning it's available application-wide unless overridden).
You can override this token at different levels in your application's dependency injection hierarchy.
Providing in an NgModule
// app.module.ts or a shared feature module
import { NgModule } from '@angular/core';
import { LMN_VALIDATION_ERROR_STATE_MATCHER, onDirtyOrSubmittedErrorStateMatcher } from '@elemental/ui';
@NgModule ({
providers: [
{
provide: LMN_VALIDATION_ERROR_STATE_MATCHER,
useValue: onDirtyOrSubmittedErrorStateMatcher,
},
],
})
export class AppModule {} // or YourFeatureModule
Providing in a Standalone Component
For standalone components, you can provide a custom default matcher directly in the component's metadata. This override will apply to this component and its children (unless they also override it).
// my-standalone-form.component.ts
import { Component } from '@angular/core';
import { ReactiveFormsModule , FormControl } from '@angular/forms';
import {
LMN_VALIDATION_ERROR_STATE_MATCHER,
onDirtyOrSubmittedErrorStateMatcher,
LmnDynamicValidatorMessageDirective,
LmnValidatorMessageContainerDirective,
} from '@elemental/ui';
@Component ({
selector: 'app-my-standalone-form',
imports: [ReactiveFormsModule , LmnDynamicValidatorMessageDirective, LmnValidatorMessageContainerDirective],
template: `
<input type="text" [formControl]="myControl" lmnDynamicValidatorMessage [errorsContainer]="errorsRef.container" />
<div>
<ng-container lmnValidatorMessageContainer #errorsRef="lmnValidatorMessageContainer"></ng-container >
</div>
`,
providers: [
{
provide: LMN_VALIDATION_ERROR_STATE_MATCHER,
useValue: onDirtyOrSubmittedErrorStateMatcher, // This component will use this matcher by default
},
],
})
export class MyStandaloneFormComponent {
protected readonly myControl = new FormControl ('');
}
Validation Error Messages
This system allows for global and local customization of the text displayed for each validation error.
LmnErrorMessages
Type
Defines the structure for providing error messages. It's a record where keys are validator error names (e.g., required
, minlength
) and values can be:
- A
string
. - A function
(args?: any) => string
that receives the error object value and returns a string. - An Angular
.Signal <string>
export type LmnErrorMessages = Record<string, ((args?: any) => string) | string | Signal <string>>;
LMN_STANDARD_ERROR_MESSAGES
A predefined LmnErrorMessages
object containing messages for common Angular validators:
export const LMN_STANDARD_ERROR_MESSAGES: LmnErrorMessages = {
required: `This field is required`,
requiredTrue: `This field is required`,
minlength: ({ requiredLength }) => `The length should be at least ${requiredLength} characters`,
maxlength: ({ requiredLength }) => `The length should be max ${requiredLength} characters`,
pattern: `Wrong format`,
};
LMN_VALIDATION_ERROR_MESSAGES
Token
This
provides the map of error messages used by LmnDynamicValidatorMessageDirective
and LmnErrorMessagePipe
.
- Default Factory: Returns
LMN_STANDARD_ERROR_MESSAGES
. - Provided in:
root
(available application-wide unless overridden).
Messages provided via the customErrorMessages
input on the LmnDynamicValidatorMessageDirective
will always take precedence over messages from this token for that specific directive instance.
Providing LMN_VALIDATION_ERROR_MESSAGES
in an NgModule
// app.module.ts or a shared feature module
import { NgModule } from '@angular/core';
import { LMN_VALIDATION_ERROR_MESSAGES, LMN_STANDARD_ERROR_MESSAGES } from '@elemental/ui';
const myAppErrorMessages: LmnErrorMessages = {
...LMN_STANDARD_ERROR_MESSAGES,
required: `This field cannot be empty!`, // Override standard 'required'
email: `Please provide a valid email address.`,
customValidatorKey: (errorValue) => `Error code: ${errorValue.code}`,
};
@NgModule ({
// ...
providers: [
{
provide: LMN_VALIDATION_ERROR_MESSAGES,
useValue: myAppErrorMessages,
},
],
})
export class AppModule {} // or YourFeatureModule
Providing LMN_VALIDATION_ERROR_MESSAGES
in a Standalone Component
Override default error messages for a specific standalone component and its children.
// my-custom-messages-form.component.ts
import { Component } from '@angular/core';
import { ReactiveFormsModule , FormControl , Validators } from '@angular/forms';
import {
LMN_VALIDATION_ERROR_MESSAGES,
LMN_STANDARD_ERROR_MESSAGES,
LmnDynamicValidatorMessageDirective,
LmnValidatorMessageContainerDirective,
} from '@elemental/ui';
import type { LmnErrorMessages } from '@elemental/ui';
const componentSpecificMessages: LmnErrorMessages = {
...LMN_STANDARD_ERROR_MESSAGES, // Optionally include standard messages
required: `You MUST fill this in!`,
minlength: ({ requiredLength }) => `Too short! Needs ${requiredLength} characters for this special field.`,
};
@Component ({
selector: 'app-my-custom-messages-form',
imports: [ReactiveFormsModule , LmnDynamicValidatorMessageDirective, LmnValidatorMessageContainerDirective],
template: `
<input
type="text"
[formControl]="specialField"
lmnDynamicValidatorMessage
[errorsContainer]="errorsRef.container"
/>
<div>
<ng-container lmnValidatorMessageContainer #errorsRef="lmnValidatorMessageContainer"></ng-container >
</div>
`,
providers: [
{
provide: LMN_VALIDATION_ERROR_MESSAGES,
useValue: componentSpecificMessages,
},
],
})
export class MyCustomMessagesFormComponent {
protected readonly specialField = new FormControl ('', [Validators .required, Validators .minLength(5)]);
}
Utilities
LmnErrorMessagePipe
A pipe to manually translate a validator error key (and its value) into a human-readable message. It uses the messages provided by the LMN_VALIDATION_ERROR_MESSAGES
token.
- Name:
lmnErrorMessage
Usage of LmnErrorMessagePipe
This is useful if you are manually handling error display and just need the message string. Ensure LmnErrorMessagePipe
is imported into your standalone component or NgModule.
<!-- my-component.component.html -->
<!-- Assume myControl is a property on your component -->
@if (myControl.hasError('minlength')) {
<div>{{ 'minlength' | lmnErrorMessage : myControl.getError('minlength') }}</div>
} @if (myControl.hasError('required')) {
<div>{{ 'required' | lmnErrorMessage }}</div>
}
<!-- Iterating over errors -->
@for (error of myControl.errors | keyvalue; track error.key) {
<div>
<span>{{ error.key | lmnErrorMessage : error.value }}</span>
</div>
} @empty {
<p>No errors to display manually.</p>
}
// my-component.component.ts
import { Component } from '@angular/core';
import { FormControl , Validators , ReactiveFormsModule } from '@angular/forms';
import { LmnErrorMessagePipe } from '@elemental/ui';
// Import KeyValuePipe if you prefer using it with @for
// import { KeyValuePipe } from '@angular/common';
@Component ({
selector: 'app-my-manual-errors',
imports: [ReactiveFormsModule , LmnErrorMessagePipe, KeyValuePipe ],
templateUrl: './my-component.component.html',
})
export class MyManualErrorsComponent {
myControl = new FormControl ('', [Validators .required, Validators .minLength(5)]);
constructor() {
// Example: Touch the control to make errors appear for the demo
this.myControl.markAsTouched();
this.myControl.setValue('abc'); // To trigger minlength
}
}
Supporting Types
LmnValidateOnEvent
Specifies the type of DOM event that can trigger validation checks by LmnDynamicValidatorMessageDirective
.
export type LmnValidateOnEvent = 'blur' | 'change' | 'input ' | 'focus' | 'focusout';
LmnDynamicError
Represents the structure of an error object as tracked by LmnDynamicValidatorMessageDirective.renderedErrors
.
export type LmnDynamicError = {
errorName: string; // e.g., 'required'
errorId: string; // DOM ID of the rendered error message component
};
Comprehensive Usage Example (Manual Application)
This example demonstrates using LmnDynamicValidatorMessageDirective
and LmnValidatorMessageContainerDirective
with standard HTML inputs, customizing messages, and using a specific error state matcher.
// my-form.component.ts
import { Component } from '@angular/core';
import { FormControl , FormGroup , Validators , ReactiveFormsModule } from '@angular/forms';
import {
LmnErrorStateMatchers,
onTouchedOrSubmittedErrorStateMatcher,
LmnDynamicValidatorMessageDirective,
LmnValidatorMessageContainerDirective,
} from '@elemental/ui';
import type { LmnErrorMessages } from '@elemental/ui';
@Component ({
selector: 'app-my-generic-form',
imports: [ReactiveFormsModule , LmnDynamicValidatorMessageDirective, LmnValidatorMessageContainerDirective],
templateUrl: './my-form.component.html',
styleUrls: ['./my-form.component.css'],
})
export class MyGenericFormComponent {
myForm = new FormGroup ({
name: new FormControl ('', [Validators .required, Validators .minLength(3)]),
email: new FormControl ('', [Validators .required, Validators .email]),
});
nameCustomMessages: LmnErrorMessages = {
minlength: ({ requiredLength }) => `Hey, name needs at least ${requiredLength} chars!`,
};
// Using a pre-built matcher directly
emailErrorMatcher = onTouchedOrSubmittedErrorStateMatcher;
// Or an object of matchers:
// emailErrorMatcherObj: LmnErrorStateMatchers = {
// default: onTouchedOrSubmittedErrorStateMatcher,
// email: (control, isFormSubmitted, readonly) => /* custom logic */,
// };
onSubmit() {
if (this.myForm.valid) {
console.log('Form Submitted!', this.myForm.value);
} else {
console.log('Form is invalid');
this.myForm.markAllAsTouched(); // Often useful with onTouched... matchers
}
}
}
<!-- my-form.component.html -->
<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="name">Name:</label>
<input
type="text"
id="name"
formControlName="name"
lmnDynamicValidatorMessage
[errorsContainer]="nameErrorsRef.container"
[customErrorMessages]="nameCustomMessages"
placeholder="Enter your name"
/>
<div>
<ng-container lmnValidatorMessageContainer #nameErrorsRef="lmnValidatorMessageContainer"></ng-container>
</div>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input
type="email"
id="email"
formControlName="email"
lmnDynamicValidatorMessage
[errorsContainer]="emailErrorsRef.container"
[errorStateMatcher]="emailErrorMatcher"
placeholder="Enter your email"
/>
<div>
<ng-container lmnValidatorMessageContainer #emailErrorsRef="lmnValidatorMessageContainer"></ng-container>
</div>
</div>
<button type="submit">Submit</button>
</form>
/* my-form.component.css */
[data-lmn-has-error] {
/* some custom styles for the host element when an error is present */
}
Integration with Elemental UI Input Components
Elemental UI input components (e.g., lmn-input-text
, lmn-input-select
) provide seamless integration with the dynamic validation message system described above. They achieve this by using the LmnDynamicValidatorMessageDirective
as a host directive and re-exposing all directive inputs to outside.
Key Advantages:
- Automatic Application: You don't need to manually add
lmnDynamicValidatorMessage
to Elemental UI input components; it's built-in. - Simplified Configuration: Inputs of the
LmnDynamicValidatorMessageDirective
(likeerrorStateMatcher
,customErrorMessages
,readonly
,negative
) are directly available as inputs on the Elemental UI components themselves. - Built-in Error Container: Each input component has a designated area within its template where error messages are rendered by default. You typically don't need to specify an
errorsContainer
.
How it Works (Example: lmn-input-text
)
The LmnInputTextComponent
(and similar components) includes LmnDynamicValidatorMessageDirective
in its hostDirectives
:
// Simplified from lmn-input-text.component.ts
@Component ({
// ...
hostDirectives: [
{
directive: LmnDynamicValidatorMessageDirective,
inputs: dynamicValidatorMessageInputs, // Re-exposes directive inputs to lmn-input-text
},
],
})
export class LmnInputTextComponent implements OnInit {
protected readonly dynamicErrors = viewChild .required('dynamicErrors', { read: ViewContainerRef });
#lmnDynamicValidatorMessage!: LmnDynamicValidatorMessageDirective;
ngOnInit(): void {
this.#lmnDynamicValidatorMessage = this.injector.get(LmnDynamicValidatorMessageDirective);
// The LmnInputTextComponent tells the host directive where to render errors within its own template.
this.#lmnDynamicValidatorMessage.setDefaultErrorContainer(this.dynamicErrors());
}
// ...
}
The lmn-input-text.component.html contains an <
where these messages are placed.
Using Dynamic Validation with Elemental UI Components
Basic Usage (lmn-input-text
)
Validation errors automatically appear within the lmn-input-text
component.
// my-form-with-input-text.component.ts
import { Component } from '@angular/core';
import { FormControl , Validators , ReactiveFormsModule } from '@angular/forms';
import { LmnInputTextComponent } from '@elemental/ui';
@Component ({
selector: 'app-my-form-with-input-text',
imports: [ReactiveFormsModule , LmnInputTextComponent],
template: `
<form (ngSubmit)="onSubmit()">
<lmn-input-text [formControl]="nameControl" placeholder="Enter your name">
<lmn-input-label>Name</lmn-input-label>
</lmn-input-text>
<button type="submit">Submit</button>
</form>
`,
})
export class MyFormWithInputTextComponent {
nameControl = new FormControl ('', [Validators .required, Validators .minLength(3)]);
onSubmit() {
if (this.nameControl.invalid) {
console.log('Name control is invalid');
}
}
}
In this example, if the field is left empty and blurred (or the form submitted, depending on the default errorStateMatcher
), the "required" error message will automatically appear beneath the lmn-input-text
field.
Customizing Validation on Elemental UI Components
Use the re-exposed inputs directly on the component (e.g., lmn-input-text
):
Custom
errorStateMatcher
:<lmn-input-text [formControl]="emailControl" [errorStateMatcher]="customEmailMatcher"> <lmn-input-label>Email</lmn-input-label> </lmn-input-text>
For details on
LmnErrorStateMatcher
types and pre-built functions, refer to the mainError State Matching section.Custom Error Messages (
customErrorMessages
):<lmn-input-text [formControl]="passwordControl" [customErrorMessages]="passwordSpecificMessages"> <lmn-input-label>Password</lmn-input-label> </lmn-input-text>
For details on the
LmnErrorMessages
type, refer to the mainValidation Error Messages section.Component Inputs (
readonly
,negative
): These inputs on Elemental UI components are respected by the integrated validation system.<lmn-input-text [formControl]="nameControl" [readonly]="isFormFieldReadonly" [negative]="isDarkTheme"> <lmn-input-label>Name</lmn-input-label> </lmn-input-text>
Overriding the Error Container (Advanced): Though usually not needed, you can redirect errors to an external container.
<lmn-input-text [formControl]="notesControl" [errorsContainer]="customNotesErrorsRef.container"> <lmn-input-label>Notes</lmn-input-label> </lmn-input-text> <ng-container lmnValidatorMessageContainer #customNotesErrorsRef="lmnValidatorMessageContainer"></ng-container>
Note: This will render errors in
customNotesErrorsRef.container
instead of the default location withinlmn-input-text
.
Styling
Elemental UI input components typically have their own internal styling for error states (often triggered by the data-lmn-has-error
attribute set by the host LmnDynamicValidatorMessageDirective
). You can add further custom styles if needed:
/* lmn-input-text itself will have a [data-lmn-has-error] attribute
when the internal LmnDynamicValidatorMessageDirective detects errors.
All the Elemental inputs like lmn-input-text already have its own internal error styling, but you can add further custom styles if needed.
*/
lmn-input-text[data-lmn-has-error] {
/* ... */
}