Skip to content

Latest commit

 

History

History
87 lines (71 loc) · 3.1 KB

async_validators.md

File metadata and controls

87 lines (71 loc) · 3.1 KB

Async Validators

Up to this point, our validation logic is living in the front-end, but what happens if we want to check for some logic that only exists in the server? For example, let's say we want to prevent two users trying to register using the same email. To do that, we must verify that the email entered does not exist in our database and we want to alert the user as he/she types. This is where the async validator comes in handy.

Our new async validator is going to live inside the CustomValidators class and the return value of the static method is going to be exactly the same as the method emailFormat, so before writing our new validation rule let's do a simple refactoring to clean the code a bit.

app/validators.ts

// ...

interface IValidation {
  [key: string]: boolean;
}

export class CustomValidators {
  static emailFormat(control: Control): IValidation {
    let pattern:RegExp = /\S+@\S+\.\S+/;
    return pattern.test(control.value) ? null : {"emailFormat": true};
  }
}

We have created an interface to define the return value of our methods so we can use the same interface with our new validator.

An async validator must return a promise that should resolve to an object with the error when our server responds with a failed validation, or should resolve to null when the server responds with a successful validation.

app/validators.ts

// ...

export class CustomValidators {
  // ...
  static duplicated(control: Control) {
    const q = new Promise<IValidation>((resolve, reject) => {
      setTimeout(() => {
        if(control.value === 'john.doe@gmail.com') {
          resolve({'duplicated': true});
        } else {
          resolve(null);
        }
      }, 1000);
    });
    return q;
  }
}

We named our new validator duplicated and we used the setTimeout function to mock a call to the server that could take one second to complete.

The next step is to add the new validation method as the third argument of the Control constructor.

app/my-form.component.ts

// ...
export class MyForm {
  // ...
  constructor(builder: FormBuilder) {
    this.email = new Control('',
      Validators.compose([Validators.required, CustomValidators.emailFormat]), CustomValidators.duplicated
    );
    // ...
  }
  // ...
}

We can modify our template again to accommodate the new error message.

app/my-form.component.html

<form ...>
  <div>
    <label for="email">Email:</label>
    <input type="text" id="email" [ngFormControl]="email">
    <span *ngIf="email.pending">Checking duplication...</span>
    <ul ...>
      <!-- ... -->
      <li *ngIf="email.hasError('duplicated')">This email has been registered already</li>
    </ul>
  </div>
  <!-- ... -->
</form>

View Example

Notice that not only do we have a new error key but our field has a new state called pending that is true when Angular waits for the promise to be resolved and false otherwise. This way we can give feedback to the user that some validation is being performed in the background that could take a while to finish.