Todd Motto

Todd Motto

Owner, Ultimate Angular

Introducing ngxErrors, declarative form errors for Angular
Apr 11, 2017
4 mins read
Edit post

I’ve been working on an open source project to bring better validation to Angular’s reactive forms. In this post we’ll take a look at how to use it, why I created it and the problems it aims to solve.

Go to GitHub repo for latest updates as the API has evolved with new features since writing this article

What is ngxErrors?

If you’re familiar with AngularJS (1.x) then you’ve likely stumbled across ngMessages. It’s a really tidy form errors module that allows you to declaratively add errors to your templates using a switch-statement style approach.

ngxErrors is my stab at the beginning of achieving similar results using Angular (v2 and onwards), with a little more ease of use, less directives and making use of observables.

Problem to solve

The problem that ngxErrors aims to solve is the template side of adding validation messages to your templates. It’s specficially designed in a way to make things easy.

We’ll boot off this demo with this component code:

export class AppComponent implements OnInit {
  
  form: FormGroup;
  
  constructor(
    private fb: FormBuilder
  ) {}
  
  ngOnInit() {
    this.form = this.fb.group({
      username: ['', [Validators.required]],
      password: ['', [Validators.required, Validators.minLength(5), Validators.maxLength(12)]]
    });
  }
  
}

Nice and simple, two form controls with a few validation rules. Then, the template:

<form [formGroup]="form" (ngSubmit)="onSubmit()">
      
  <input type="text" formControlName="username">
  <div>
    <div>
      Username is required
    </div>
  </div>

  <input type="text" formControlName="password">
  <div>
    <div>
      Password is required
    </div>
    <div>
      5 characters minimum, 12 characters maximum
    </div>
  </div>
  
</form>

Looks clean, let’s then add their conditional triggers:

<form [formGroup]="form" (ngSubmit)="onSubmit()">
      
  <input type="text" formControlName="username">
  <div>
    <div 
      *ngIf="form.get('username').hasError('required') && form.get('username').touched && form.get('username').dirty">
      Username is required
    </div>
  </div>

  <input type="text" formControlName="password">
  <div>
    <div 
      *ngIf="form.get('password').hasError('required') && form.get('password').touched && form.get('password').dirty">
      Password is required
    </div>
    <div 
      *ngIf="(
        form.get('password').hasError('minlength') || form.get('password').hasError('maxlength')
      ) && form.get('password').touched && form.get('password').dirty">
      5 characters minimum, 12 characters maximum
    </div>
  </div>
  
</form>

In the words of Ron Burgundy - that escalated quickly.

Wouldn’t it be nice to add this behaviour in just a few simple steps? With ngxErrors you can! Here’s the exact same example above using ngxErrors:

<form [formGroup]="form" (ngSubmit)="onSubmit()">
      
  <input type="text" formControlName="username">
  <div ngxErrors="username">
    <div ngxError="required" [when]="['dirty', 'touched']">
      Username is required
    </div>
  </div>

  <input type="text" formControlName="password">
  <div ngxErrors="password">
    <div ngxError="required" [when]="['dirty', 'touched']">
      Password is required
    </div>
    <div [ngxError]="['minlength', 'maxlength']" [when]="['dirty', 'touched']">
      5 characters minimum, 12 characters maximum
    </div>
  </div>
  
</form>

It took me a while to get the design of this API to what I’d consider a clean and readable solution. Let’s dive into some further explanations as to how each piece works.

ngxErrors

ngxErrors requires reactive forms and a form control to use the directive on.

ngxErrors directive

Taking this example:

<input type="text" formControlName="username">

We have the formControlName with a value of "username". To kick off ngxErrors all we need to do is pass the value into it:

<input type="text" formControlName="username">
<div ngxErrors="username">
  
</div>

This will then tell ngxErrors to look for status changes for that particular form control, as well as props such as “dirty”, “pristine”, “touched” - and error states such as “required” and friends. It also supports custom validators and async validators.

ngxError directive

Next, ngxErrors requires an “ngxError” - a single validation message you wish to display:

<input type="text" formControlName="username">
<div ngxErrors="username">
  <div ngxError="required">
    Password is required
  </div>
</div>

The ngxError directive accepts a string or array of values to then match against their particular error, giving us three possible syntaxes:

<div ngxError="required">
  Password is required
</div>
<div [ngxError]="'required'">
  Password is required
</div>
<div [ngxError]="['required', 'minlength']">
  Password is required
</div>

When using the array syntax, you’ll need to remember to [] data bind the values.

ngxError #when

The when directive is the controller of visibility rules. Ideally we don’t want to display messages at runtime without user interaction so we can hook into "touched" or similar to await user interaction:

<input type="text" formControlName="username">
<div ngxErrors="username">
  <div ngxError="required" when="touched">
    Password is required
  </div>
</div>

This also supports a string as well as array syntaxes for multiple conditions:

<div ngxError="required" when="touched">
  Password is required
</div>
<div ngxError="required" [when]="'touched'">
  Password is required
</div>
<div ngxError="required" [when]="['touched', 'dirty']">
  Password is required
</div>

Dynamic errors

With ngxErrors you can also dynamically render out your messages:

@Component({
  selector: 'app-root',
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <input type="text" formControlName="username">
      <div ngxErrors="person.username">
        <div 
          *ngFor="let error of errors" 
          [ngxError]="error.name" 
          [when]="error.rules">
          {{ error.text }}
        </div>
      </div>
    </form>
  `
})
export class AppComponent implements OnInit {
  errors = [
    { name: 'required', text: 'This field is required', rules: ['touched', 'dirty'] },
    { name: 'minlength', text: 'Min length is 5', rules: ['dirty'] }
  ];
  //...
}

Live demo

Check out the live demo below:

Installing it

You can check out the GitHub repo for further documentation.

To install in your project is super simple:

yarn add @ultimate/ngxerrors

# OR
npm i @ultimate/ngxerrors

From there, you’ll just include it in your @NgModule and off you go:

import { NgxErrorsModule } from [email protected]/ngxerrors';

@NgModule({ imports: [ NgxErrorsModule ] })
export class AppModule {}

And you’re good to go!

Summing up

I’m sure this is just the beginning of the library, but I think it’s headed in a good direction to achieve easy validation with Angular’s reactive forms. Hope you make use of it!

Mar 13, 2017

Configurable Reactive Forms in Angular with dynamic components

In this post we’re going to explore the creation of dynamic components alongside a Reactive...

May 8, 2017

Component architecture recipes for Angular’s reactive forms

Component architecture is the fundamental building block of applications, and isn’t just limited to Angular....