DEV Community

Jeffrey Bosch
Jeffrey Bosch

Posted on

Template validations made easy with Validointi

Have you ever created an Angular template form? Did you like the way you needed to do validations if it goes a bit further than required? Here comes a pain for many Angular developers and probably one of the reasons why a lot of Angular developers choose reactive forms over template forms.

TLDR

The article explains how to use Validointi, a library that makes it easy to perform validations for Angular template forms beyond simple validations like "required". Validointi allows developers to use schema validation libraries like Vest, AJV, or Joi with Angular. The article covers basic concepts like Model, Validation Library, and Adapters and provides a step-by-step guide to hook up Validointi to an Angular template form.

In this article, we are going to have a look at an easy way to use template forms and go beyond a simple required validation by using Validointi. An easy to use library for Angular developers with a powerful setup to have a great developer experience.

Introduction to Validointi

To understand everything that we will cover in this article I will explain a couple of the basic concepts of Validointi.

Model

A model contains the information that we will use in our form. Below is an example of a simple model that represents a simple form. The model should always contain information and never any behavior. In the example below we have a simple model with a definition for: name, email, password and a confirm password. This model will be used during the reset of the article as a baseline.

{
    name: 'Your name',
    email: 'info@example.org',
    password: 'a-p-a-s-s-w-o-r-d',
    confirmPassword: 'a-p-a-s-s-w-o-r-d'
}
Enter fullscreen mode Exit fullscreen mode

Validation library

By using Angular and more specifically Angular Forms, you will be provided by a couple of default validators like required, min, max and regex. The provided out of the box validators are directives that represent the HTML5 standard. When a form is getting more complex, we need to create custom validations. These validations can be plain functions with for example a regex, but we need to hook up the function to a directive to apply the validation to the template field.

This is a lot of work for just a simple regex tests and most of the Angular devs don’t like this approach. In several other frontend frameworks, you can use schema validation libraries like Vest, AJV or Joi. In Angular this is not a “common” approach however, here is where Validointi comes into place. Validointi makes it possible to use these kinds of libraries easily with Angular. More on this later.

Adapters

This is the last part of the concepts and probably this is the easiest one. Adapters do the heavy lifting to attach a library as Vest to Angular. Adapters are plain functions and can be created for a custom validation library. Today we will only use the Vest adapter.

Hooking up Validointi to Angular

First let us set up the Angular template form, be aware we will use the new standalone API that has been introduced in Angular v15. In this article, we will not go into the exact details of template form.

The form is pretty straightforward it is a registration form with a name, email, password and a confirm password. As we are using template forms we need to bind the model with ngModel.

<form  (ngSubmit)="onSubmit($any(form))" #form="ngForm">
  <label for="name">
    <span>Your name</span>
  </label>
  <input
    type="text"
    name="name"
    placeholder="Fill in your name"
    [(ngModel)]="model.name"
  />

  <label for="email">
    <span>Your email</span>
  </label>
  <input
    type="email"
    name="email"
    placeholder="Fill in your email"
    [(ngModel)]="model.email"
  />
  <label for="password">
    <span>Your password</span>
  </label>
  <input
    type="password"
    name="password"
    placeholder="Fill in your password"
    [(ngModel)]="model.password"
  />
  <label for="confirmPassword">
    <span>Your confirm password</span>
  </label>
  <input
    type="password"
    name="confirmPassword"
    placeholder="Fill in your confirm password"
   [(ngModel)]="model.confirmPassword"
  />

  <input type="submit" value="Submit" [disabled]="form.invalid" />
</form>
Enter fullscreen mode Exit fullscreen mode

Now that we know how the HTML looks like have a look at the component code. The code is simple, we import the FormsModule, define a empty model and have a function that will be used when submitting the form.

import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

interface Model {
  name: string;
  email: string;
  password: string;
  confirmPassword: string;
}

@Component({
  selector: 'app-template-form',
  standalone: true,
  imports: [CommonModule, FormsModule],
  templateUrl: './template-form.component.html',
  styleUrls: ['./template-form.component.css'],
})
export class TemplateFormComponent {
  model: Model = { name: '', email: '', confirmPassword: '', password: '' };

  async onSubmit(data: any) {
  }
}
Enter fullscreen mode Exit fullscreen mode

As the basics are set now we are going to hookup Validointi. First we import the directives that Validointi provides.

import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
+ import {
+  createVestAdapter,
+  ValidatorRegistryService,
+  ValidatorDirective,
+ } from '@validointi/core';
+ import { create, enforce, test } from 'vest';

@Component({
  selector: 'app-template-form',
  standalone: true,
- imports: [CommonModule, FormsModule],
+ imports: [CommonModule, FormsModule, ValidatorDirective],
  templateUrl: './template-form.component.html',
  styleUrls: ['./template-form.component.css'],
})
Enter fullscreen mode Exit fullscreen mode

We can now make the validations for our model. As we explained earlier we will use Vest in our example and that is why we have imported it.

Vest works with a suite, a suite is similar to a unit testing suite in Jest or Mocha. The following part will be recongnicable. In this suite we will validate the model with different rules, these rules can be sync or async.

const suite = create((data: Model = {} as Model, field?: string) => {
  test('name', 'Name is required', () => {
    enforce(data.name).isNotBlank();
  });

  test('name', 'Name must be at least 3 characters long', () => {
    enforce(data.name).longerThan(2);
  });

  test('email', 'Email is required', () => {
    enforce(data.email).isNotBlank();
  });
  test('email', () => {
    enforce(data.email)
      .message('Not an valid email address')
      .matches(/^[^@]+@[^@]+$/);
  });

  test('password', 'Password is required', () => {
    enforce(data.password).isNotEmpty();
  });
  test('password', 'Password is too short', () => {
    enforce(data.password).longerThan(2);
  });
  test('password', 'Password is weak. maybe add a number', () => {
    enforce(data.password).matches(/[0-9]/);
    enforce(data.password).longerThanOrEquals(6);
  });

  test('confirmPassword', 'Passwords do not match', () => {
    enforce(data.confirmPassword).equals(data.password);
  });
});
Enter fullscreen mode Exit fullscreen mode

The only missing part is to hookup the test suite with the Angular form. Here is where validointi is coming into play. And, it is SOO easy, let’s see some code

@Component({
  selector: 'app-template-form',
  standalone: true,
  imports: [CommonModule, FormsModule, ValidatorDirective],
  templateUrl: './template-form.component.html',
  styleUrls: ['./template-form.component.css'],
})
export class TemplateFormComponent {
  model: Model = { name: '', email: '', confirmPassword: '', password: '' };

+  #vr = inject(ValidatorRegistryService);
+  validate = this.#vr.registerValidator('form1', createVestAdapter(suite));

  async onSubmit(data: any) {
+    const validationResult = await this.validate(data);
+    console.dir(validationResult);
  }
}
Enter fullscreen mode Exit fullscreen mode
- <form (ngSubmit)="onSubmit($any(form))" #form="ngForm">
+ <form validationId="form1" (ngSubmit)="onSubmit($any(form))" #form="ngForm">
Enter fullscreen mode Exit fullscreen mode

In the code we used registered our validation by using the ValidatorRegistryService and added our Vest Suite to a validation function. When submitting a form we will validate the form with our test suite. In the HTML we will see the different error states when the test suite returns an invalid response.

As we can see we have separated all of our test logic to the test suite and by using a simple service + adapter provided by Validointi we can use the test suite in our Angular template form.

Below is a Stackblitz example of the complete project that you can use.

Bonus

Personally, I’m a believer in keeping your components as thin as possible and deriving logic to services. In our example we added test logic, logic to hookup validointi etc. In our example it is still pretty simple but you can imagen that when there are more complex forms and validation rules this is not the best way.

That is why I made the following changes to make our component thin again.

  1. Removed all the test logic and moved it to the data-service.ts
  2. Removed the validointi logic and moved it to the data-service.ts
  3. Injected the data-service.ts and use it in our submit function.

Now our component is thin again and we have separated our “business logic” to a service (where it should be in the first place)

Summary

In summary, Validointi makes it easy to perform “complex” validations in Angular template forms without the hassle of making directive and validation functions. Validointi allows developers to use schema validation libraries like Vest, AJV, or Joi with Angular.

Thank you for reading this article and if you are interested in Validointi, have a look at: https://validointi.github.io. I would like to thank Sander Elias for proof reading the article

You can always hang out with me in one of the Dutch Angular Events or send me a DM at @jefiozie

Top comments (0)