Unique username validator (Angular)

Form validation in Angular is a well-known mechanism to perform client-side validation. This way user receives rapid feedback about the data put into the form. It may spare a lot of time as form submission usually takes a few seconds so it would be cumbersome to submit the form several times until all inputs contain valid data.

Some validators are already built-in in Angular (Angular – Validators) but you can also add custom validators. This article describes how to use this mechanism to provide quick information about the username uniqueness – before the form is submitted.

Validator types

There are two types of validators supported by Angular:

  • Synchronous validators – they are fired first and usually work very fast, providing immediate feedback as they don’t need to wait for any additional data to validate input. Such validators implement Validator interface (or ValidatorFn if they are functions).
  • Asynchronous validators – they are executed once synchronous validation is done and there are no errors from the validation to limit unnecessary asynchronous validation triggers as asynchronous validators usually perform more costly operations, e.g. calls to HTTP endpoints. It also means that this kind of validation provides results a little bit later – once the asynchronous operation is completed.
    Before the asynchronous validation is done – the form’s status is set to PENDING.
    Such validators implement AsyncValidator interface (or AsyncValidatorFn).

Unique username validator is an example of the second type as it needs to check the uniqueness of provided username with the backend via an HTTP call.

Reactive forms vs template-driven forms

Even though reactive forms seem to be a better choice than template-driven ones for most applications, the second type is still used in many places. That’s why I decided to provide an example of the validator for both types of forms.

Actually, the implementation is nearly the same. Both validators are classes implementing AsyncValidator interface with the same in the class. Only class annotations are different:

  • Reactive forms – it’s @Injectable so it can be injected into component where the validator is attached to Reactive Form,
  • Template-driven forms – it’s @Directive so it can be registered as a directive in the module and used in the form’s template (in HTML code)

More information about the comparison between these two types of forms can be found here: Angular Forms Guide — Reactive Forms Vs. Template-driven Form

Implementation

User service to check if the user exists

Let’s start with a service providing information we need to perform the validation – whether user with given username already exists or not.

@Injectable({ providedIn: 'root' })
export class UserService {
  constructor(private http: HttpClient) {}

  userExists(username: string): Observable<boolean> {
    return this.http
      .get<User[]>('api/users', { params: { username } })
      .pipe(map((matchingUsers) => matchingUsers.length > 0));
  }
}

This service provides userExists method doing just what we need. It sends the request to an HTTP endpoint trying to get details of users with a given username – such an endpoint returns one of two responses:

  • an empty array – means that no user with a given username was found,
  • an array with a single user – means that a user with a given username was found.

The code simply checks if any user with a given username was found and maps the response to boolean using this condition.

There are many other possible ways to implement it, e.g.:

  • dedicated endpoint for username uniqueness validation which simply returns true/false (or unique/non-unique) information,
  • usage of something else than REST, e.g. GraphQL or gRPC.

I just wanted to create a RESTful API here and this is how I achieved it. It doesn’t mean that other ways are worse (or better) – it all depends on your preferences and on the way you chose for your project so I won’t take deep dive into this topic here.

Validator

Here is the base code of the validator, regardless of form type (only the class name and class annotations will be different as mentioned earlier).

export class UniqueUsernameValidator implements AsyncValidator {
  private static readonly USERNAME_DUPLICATED = { usernameDuplicated: true };
  private static readonly USERNAME_NOT_DUPLICATED = null;

  constructor(private userService: UserService) {}

  validate(
    control: AbstractControl
  ): Promise<ValidationErrors> | Observable<ValidationErrors> {
    const username = control.value;
    return this.userService.userExists(username).pipe(
      map((exists) =>
        exists
          ? UniqueUsernameValidator.USERNAME_DUPLICATED
          : UniqueUsernameValidator.USERNAME_NOT_DUPLICATED
      ),
      first()
    );
  }
}

It implements AsyncValidator interface and provides validate method from the interface. According to the interface’s specification, this method should return:

  • null if there are no errors, in this case – if the user with given username does not exist so it can be used,
  • an object containing a list of errors if there should be a validation error, i.e. if the user with a given username exists. I followed the usual convention where the key is the error’s identifier (name) and the value is simply true confirming this error occurred (in other validators there may be details, e.g. built-in minValidator returns information about the current text’s length). Still, essentially this object can be anything else, just not null.

UserService is injected into this validator and its userExists method is used to perform the validation by converting the boolean value it returns to the proper output value (null or errors object).

Additionally, first operator is applied to make sure that the validation is finished. In this application’s context, it could be skipped because userExists method returns always a single value so Observable will be finite (i.e. it will return some values and finish its job – in this case, it returns always one value) anyway. But to make this implementation more safe and independent of whether userExists returns finite or infinite Observable – this first operator is applied to take only the first value and finish validation by ignoring the others. Of course, it applies only to single validation so any input’s value change triggers new validation.

Validator for reactive forms

It is enough to make the validator injectable and then apply it to form control in the same way as the other validators.

@Injectable()
export class UniqueUsernameValidator implements AsyncValidator {
  // validator's implementation as presented above
}

In my example, I used FormBuilder to build a form and declare validators.

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [UniqueUsernameValidator],
})
export class AppComponent implements OnInit {
  registrationForm: FormGroup;

  constructor(
    private fb: FormBuilder,
    private uniqueUsernameValidator: UniqueUsernameValidator
  ) {}

  ngOnInit(): void {
    this.registrationForm = this.fb.group({
      username: [
        '',
        [Validators.required, Validators.minLength(3)],
        [this.uniqueUsernameValidator],
      ],
    });
  }
}

Username validation is performed in two steps:

  • Synchronous validators are used at the beginning so until the text of length 3 is provided, username uniqueness won’t be checked,
  • An asynchronous validator (UniqueUsernameValidator) is fired once synchronous validation is successful and if the username is unique – the control and hence the whole form will be marked as valid.
Validator for template-drive forms

For template-driven forms, the validator must be a directive.

@Directive({
  selector: '[unique-username-validator]',
  providers: [
    {
      provide: NG_ASYNC_VALIDATORS,
      useExisting: UniqueUsernameValidatorDirective,
      multi: true,
    },
  ],
})
export class UniqueUsernameValidatorDirective implements AsyncValidator {
  // validator's implementation as presented above
}

Remember also about its declaration in app.module.ts

@NgModule({
  imports: [ ... ],
  declarations: [ ... , UniqueUsernameValidatorDirective],
  bootstrap: [AppComponent],
})
export class AppModule {}

This way validator’s directive can be used in template-driven forms – it must be applied directly in HTML on proper input (unique-username-validator directive).

<form #registrationTemplateDrivenForm="ngForm">
  <input
      id="username"
      type="text"
      name="username"
      unique-username-validator
      ngModel
      #username="ngModel"
      autocomplete="off"
  />
</form>

Final notes

This validator can be easily changed to validate the uniqueness of email or other fields. It can also be changed to invoke other kinds of backend endpoints to perform form validations. Such validators can be really powerful and provide a lot of value to the client.

Please remember that this mechanism can potentially involve a lot of additional HTTP calls (or other expensive operations) so it’s better to use it reasonably and apply only in places where it really improves the user’s experience.

Code

The full code can be found in my GitHub’s repository. In this post, I presented only the most important parts.

I prepared also StackBlitz where you can run, test and modify it online.

Additional knowledge

Leave a Reply

Your email address will not be published. Required fields are marked *