Aurelia validation: Working with the new alpha release

by Roman Fürst
Tags: JavaScript , Aurelia , Tutorial , Open Source , TypeScript , Validation

While being outdated, my last post on aurelia-validation is still one of the most read articles in this blog. Obviously there is a need so I decided to write a follow-up.

Creating a new project

Version 0.12.x of aurelia-validation introduced some breaking changes compared to version 0.11.x. Most importantly, aurelia-validation does not depend on validate.js anymore. So there are obviously some API changes. Also the decorator API has been removed for now (it might come back soon).

Ok, now let's jump right in: Install aurelia and create a new project:

$ sudo npm install -g aurelia-cli
$ au new aurelia-validation-tutorial

As in my previous article, I'm going with the default TypeScript installation. Again, you are free to choose whatever fits you best of course. The concepts are still the same.

Setting up validation

First we need to install aurelia-validation:

$ npm install aurelia-validation --save

Because aurelia-validation does not depend on validate.js anymore, aurelia-validatejs and validatejs are obsolete. We append --save to include our new dependency in the project's package.json.

Next we will have to add aurelia-validation to our project. This step will become obsolete once aurelia-cli supports the installation of 3rd-party libraries. Until then we just append a few lines under dependencies of our vendor-bundle in aurelia_project/aurelia.json:

{
    "name": "aurelia-validation",
    "path": "../node_modules/aurelia-validation/dist/amd",
    "main": "aurelia-validation"
}

Then we register aurelia-validation as a plugin. Open src/main.ts and update Aurelia's configuration:

aurelia.use
  .standardConfiguration()
  .feature("resources")
  .plugin("aurelia-validation");

Creating a simple form

Now that we have our dependencies installed, we are going to create a form that holds our data and two custom elements which collect user input and contain our validation rules. This way we will end up having reusable, loosely coupled custom elements that can be validated out of a parent element, the actual form in our case.

src/resources/elements/person-detail.ts

Let's create our first custom input element:

export class PersonDetail {
  @bindable
  public person;

We use the bind() callback to hook in our validation rules using the fluent API. There are many other validators beside requiredof course. Take a look at the official Aurelia docs. In the following example we are addressing the property names with strings. If you are using TypeScript like me, you probably want to pass access expressions instead. You will benefit from type checking, better refactroing support and avoid magic strings. We will use access expressions in the next example further below.

  public bind() {
    ValidationRules
      .ensure("firstName").required()
      .ensure("lastName").required().withMessage("My custom error message")
      .on(this.person);
  }
}

src/resources/elements/person-detail.html

The view is straight-forward. Just remember to include the validate binding behavior. The error span is where we will render error messages later on.

<template>
  <div class="first-name">
    <label for="first">First name:</label>
    <input id="first"  type="text" value.bind="person.firstName & validate"/>
    <span class="error"></span>
  </div>
  <div class="last-name">
    <label for="last">Last name:</label>
    <input id="last" type="text" value.bind="person.lastName & validate"/>
    <span class="error"></span>
  </div>
</template>

src/resources/elements/country-detail.ts

Now the second custom input element. This time we define our validation rules using access expressions. Also we make use of the newly introduced conditional validation. The required rule is evaluated only, if the expression within when equals true. In this example, a checkbox has to be ticked.

export class CountryDetail {
  @bindable
  public name: string;

  @bindable
  public validate: boolean = false;

  public constructor() {
    ValidationRules
      .ensure((c: CountryDetail) => c.name)
        .displayName("My super fancy country")
        .required()
        .when((c: CountryDetail) => c.validate == true)
      .on(this);
  }
}

src/resources/elements/country-detail.html

<template>
  <div class="country-name">
    <div>
      <label><input type="checkbox" name="validate" checked.bind="validate">Enable validation</label>
    </div>
    <div>
      <label for="name">Name:</label>
      <input id="name" type="text" value.bind="name & validate"/>
      <span class="error"></span>
    </div>
  </div>
</template>

So far so good. Now that we have our two elements in place, let's continue by writing a custom error message renderer.

Writing a custom error message renderer

To display error messages we need to create a ValidationRenderer. Let's call our custom renderer SimpleValidationRenderer in this tutorial. This renderer does nothing more than simply insert and remove error messages in our error span elements (<span class="error"></span>).

src/resources/validation/simple-validation-renderer.ts

export class SimpleValidationRenderer {
  public render(instruction: RenderInstruction) {
    // insert new error messages
    for (let {error, elements} of instruction.unrender) {
      elements.forEach(target => target.parentElement.querySelector(".error").textContent = "");
    }

    // remove obsolete error messages.
    for (let {error, elements} of instruction.render) {
      elements.forEach(target => target.parentElement.querySelector(".error").textContent = error.message);
    }
  }

Putting everything toghether

We are almost done. Let's take our two custom elements and our custom renderer and put them together in a form. We make src/app.ts our parent view-model that holds our data. We also inject an instance of ValidationController using the NewInstance resolver. This is telling Aurelia to always inject a new instance of ValidationController instead of sharing it with other components. This also has the effect that whenever we call validate() on this specific ValidationController instance, all children elements (PersonDetail and CountryDetail in our case) will be automagically validated. Further we tell ValidationController to use our SimpleValidationRenderer to render error messages.

src/app.ts

@inject(NewInstance.of(ValidationController))
export class App {

  public person = {
    firstName: "",
    lastName: ""
  }

  public country = "";

  constructor(private validationController: ValidationController) {
    this.validationController.addRenderer(new SimpleValidationRenderer());
    // trigger validations manually
    // validationController.validateTrigger = validateTrigger.manual;
  }

  public submit() {
    this.validationController.validate()
      .then((errors: ValidationError[]) => {
        if(errors.length <= 0) {
          console.log("Validation successful!", errors);
        } else {
          console.log("Validation failed!", errors);
        }
      });
  }
}

src/app.html

The view is straight-forward again.

<template>
  <require from="resources/elements/person-detail"></require>
  <require from="resources/elements/country-detail"></require>

  <form submit.delegate="submit()">
    <h1>Person</h1>
    <person-detail person.bind="person"></person-detail>
    <h1>Country</h1>
    <country-detail name.bind="country"></country-detail>

    <button type="submit">Submit</button>
  </form>
</template>

Alright, thats it. We have successfully integrated aurelia-validation in our little project. Start Aurelia and see for yourself.

Source code

You can find the full source code for this tutorial on github. If you have any questions, suggestions or whatever, feel free to write a comment below.