ngx‑valdemort by Ninja Squad

Simple, consistent validation error messages for your Angular forms.

Tired of writing the same validation logic again, and again?

If you're using Angular forms, you have probably written code like this to display validation errors:

@if (form.controls.email.invalid && (f.submitted || form.controls.email.touched)) {
  <div class="invalid-feedback">
    @if (form.controls.email.hasError('required')) {
      <div>The email is required</div>
    }
    @if (form.controls.email.hasError('email')) {
      <div>The email must be a valid email address</div>
    }
  </div>
}

Here is a complete small form using that kind of markup.

<form [formGroup]="form" #f="ngForm">
  <div class="mb-3">
    <label class="form-label">Email</label>
    <input formControlName="email" class="form-control" type="email" />
    @if (form.controls.email.invalid && (f.submitted || form.controls.email.touched)) {
      <div class="invalid-feedback">
        @if (form.controls.email.hasError('required')) {
          <div>The email is required</div>
        }
        @if (form.controls.email.hasError('email')) {
          <div>The email must be a valid email address</div>
        }
      </div>
    }
  </div>

  <div class="mb-3">
    <label class="form-label">Age</label>
    <input formControlName="age" class="form-control" type="number" />
    @if (form.controls.age.invalid && (f.submitted || form.controls.age.touched)) {
      <div class="invalid-feedback">
        @if (form.controls.age.hasError('required')) {
          <div>The age is required</div>
        }
        @if (form.controls.age.hasError('min')) {
          <div>You must be at least {{ form.controls.age.getError('min').min }} years old</div>
        }
      </div>
    }
  </div>

  <button class="btn btn-primary me-2" (click)="submit()">Submit</button>
  <button class="btn btn-secondary" type="button" (click)="reset(f)">Reset</button>
</form>

That's a lot of code and duplications to display appropriate error messages, when the form is submitted or when the input field is touched. And this is only one form, with just two fields.

Sure, we could improve this by adding fields or getters in the component, but doing so for each and every form of the app is cumbersome, error-prone, and can lead to inconsistencies if every developer doesn't use the same technique.

Our solution: ngx-valdemort

Here is how you do the same using ngx-valdemort:

<val-errors controlName="email">
  <ng-template valError="required">The email is required</ng-template>
  <ng-template valError="email">The email must be a valid email address</ng-template>
</val-errors>

And here is the same complete small form.

<form [formGroup]="form" #f="ngForm">
  <div class="mb-3">
    <label class="form-label">Email</label>
    <input formControlName="email" class="form-control" type="email" />
    <val-errors controlName="email">
      <ng-template valError="required">The email is required</ng-template>
      <ng-template valError="email">The email must be a valid email address</ng-template>
    </val-errors>
  </div>

  <div class="mb-3">
    <label class="form-label">Age</label>
    <input formControlName="age" class="form-control" type="number" />
    <val-errors controlName="age">
      <ng-template valError="required">The age is required</ng-template>
      <ng-template valError="min" let-error="error">You must be at least {{ error.min }} years old</ng-template>
    </val-errors>
  </div>

  <button class="btn btn-primary me-2" (click)="submit()">Submit</button>
  <button class="btn btn-secondary" type="button" (click)="reset(f)">Reset</button>
</form>

This is already much better. The code is much easier to read and write, and the probability of introducing a bug is much smaller than before. But we can do better, and avoid repeating the same error messages over and over.

Consistent error messages? We've got you covered!

By defining a default error message for each type of error once, and only once (typically in the root component), every form can become simpler.

<val-default-errors>
  <ng-template valError="required" let-label>{{ label || 'This field' }} is required</ng-template>
  <ng-template valError="email" let-label>{{ label || 'This field' }} must be a valid email address</ng-template>
  <ng-template valError="min" let-error="error" let-label>
    {{ label || 'This field' }} must be at least {{ error.min | number }}
  </ng-template>
  <!-- same for the other types of error -->
</val-default-errors>

As you can see, you can choose to have very generic error messages (This field is required, as demonstrated on the age example) or, by just specifying the label on the val-errors component, have specific, but consistent error messages. And if you really need to (like in the error for the minimum age), you can override the default message by the one you want.

By defining the messages in the templates, you can easily internationalize them, use pipes, or HTML formatting.

Easily adapt ngx-valdemort to your preferences

The behavior of ngx-valdemort is configured via a configuration service. The same look and feel can thus be applied globally.

Instead of displaying errors when the control is touched or its form is submitted, the following example uses the service to specify that errors should be shown when the controls are dirty, to add a CSS class on errors to display them in orange rather than red, and to only display one error message per control instead of all of them.

Such a configuration is typically done only once, in the root component (or in a dedicated component inserted in the root component), to ensure that the same configuration applies everywhere.

constructor(config: ValdemortConfig) {
  config.errorsClasses = 'text-warning';
  config.displayMode = DisplayMode.ONE;
  config.shouldDisplayErrors = (control, form) => control.dirty;
}

It works with ngModel too!

<div class="mb-3">
  <label class="form-label">Email</label>
  <input class="form-control" type="email" name="email" [(ngModel)]="user.email" required email #emailCtrl="ngModel" />
  <val-errors [control]="emailCtrl.control" label="The email" />
</div>

Features

Easy to use

  • Minimum markup needed
  • Add val-errors, and you're set!
  • Consistent, default error messages

Configurable

  • Overriding of default messages for custom error messages
  • Access to the error value (min, max, required length, etc.)
  • Central configuration service

Customisable

  • I18n possible
  • HTML in messages possible
  • Use of pipes in messages possible
  • A fallback message can be specified to handle forgotten errors or to display multiple error types the same way using i18n (see API documentation)

Usable everywhere

  • In reactive forms, template-driven forms, or standalone controls
  • On controls nested in form groups or form arrays

Free, and open-source.

You convinced me. How to get started?

  1. import from ngx-valdemort
  2. add ValdemortModule to the imports of your module, or directly import our standalone components and directives from your components
  3. Use <val-errors>, <val-default-errors> and ValdemortConfig as shown above
  4. ...
  5. Profit!

We also have an API documentation.

[...]
import { ValdemortModule } from 'ngx-valdemort';

@NgModule({
  [...]
  imports: [
    [...]
    ValdemortModule
  ],
  [...]
})
export class AppModule { }