CDK Errors

Errors

Overview API Examples

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 ViewContainerRef.

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 NgControl and manages the display of error messages.

Inputs

InputAliasTypeDefault ValueDescription
disabledlmnDynamicValidatorDisabledbooleanfalseIf true, disables the directive, clearing any displayed errors and preventing new ones from showing.
errorStateMatcher-LmnErrorStateMatcher | LmnErrorStateMatchersLMN_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 Error State Matching.
errorsContainer-ViewContainerRef(Optional)The ViewContainerRef 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', 'input', 'focus', 'focusout'.
errorsCountToDisplay-number3Maximum number of error messages to display simultaneously. Use -1 to display all errors.
negative-booleanfalseIf true, applies a "negative" theme to the error messages (e.g., for dark backgrounds). This is passed to the underlying LmnHelperMessageComponent.
readonly-booleanfalseIndicates 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 Validation Error Messages.

Public Properties

  • renderedErrors: Signal<Map<string, LmnDynamicError>> A read-only Angular Signal that emits a map of currently rendered errors. The map keys are error names (e.g., required, minlength), and values are objects containing errorName and errorId (the DOM ID of the error message component).

Public Methods

  • setDefaultErrorContainer(container: ViewContainerRef): void Allows a parent component or directive to provide a default ViewContainerRef for rendering errors if the 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 ViewContainerRef to pass to the errorsContainer input of the LmnDynamicValidatorMessageDirective.

Properties

  • container: ViewContainerRef The ViewContainerRef of the host element, which can be directly passed to the errorsContainer input of LmnDynamicValidatorMessageDirective.

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 AbstractControl being validated.
  • isFormSubmitted: true if the parent form has been submitted.
  • readonly: true if the control is marked as read-only.
  • Returns: boolean or Observable<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 NOT readonly.
  • onTouchedOrSubmittedErrorStateMatcher: Shows errors if the control is invalid AND (control.touched OR form submitted) AND NOT readonly.
  • onTouchedAndDirtyOrSubmittedErrorStateMatcher: (Default for the system via LMN_VALIDATION_ERROR_STATE_MATCHER token) Shows errors if the control is invalid AND ((control.touched AND control.dirty) OR form submitted) AND NOT readonly.

LMN_VALIDATION_ERROR_STATE_MATCHER Token

This InjectionToken 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 InjectionToken<LmnErrorMessages> 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:

  1. Automatic Application: You don't need to manually add lmnDynamicValidatorMessage to Elemental UI input components; it's built-in.
  2. Simplified Configuration: Inputs of the LmnDynamicValidatorMessageDirective (like errorStateMatcher, customErrorMessages, readonly, negative) are directly available as inputs on the Elemental UI components themselves.
  3. 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 <ng-container #dynamicErrors /> 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):

  1. 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 main Error State Matching section.

  2. 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 main Validation Error Messages section.

  3. 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>
  4. 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 within lmn-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] {
  /* ... */
}