Xavier
Xavier

Reputation: 103

Validate Username Exist Async

I'm trying to validate a username by saying it is available or not with an async Validator, I've tried many solutions but I don't know to get this work, I have this error:

Error: Expected validator to return Promise or Observable.

My method validateUsername() is incomplete, how can I make it work ?

register.component.ts

this.registerForm = this.fb.group({
            username: ['', [Validators.required], this.validateUsername.bind(this)],
            email: ['', [Validators.required,
                Validators.pattern('[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$')]],
            password: this.fb.group({
                pwd: ['', [Validators.required, Validators.minLength(1)]], // todo
                confirmpwd: ['', [Validators.required, Validators.minLength(1)]]// todo
            }, {
                validator: this.mustMatch('pwd', 'confirmpwd'),
            }),
            birthday: ['', Validators.required]
        });
    }


validateUsername(control: AbstractControl) {
        this.userservice.checkUserExist(control.value).subscribe(
            res => {
                this.exist = 'username available ';
            },
            error => {
                this.exist = 'username unavailable ';
            }
        );
    }

register.component.html

<div class="form-group">
                <label for="inputName" class="font-weight-bold">Usuario</label>
                <div class="input-group Name">
                    <div class="input-group-prepend">
                        <div class="input-group-text"><i class="fa fa-user" aria-hidden="true"></i>
                        </div>
                    </div>
                    <input type="text" class="form-control"  id="inputName" name="inputName" formControlName="username"
                           placeholder="Identificador de usuario">
                </div>
                <div *ngIf="username.invalid && username.touched" class="col-4 p-0 ml-5 text-danger">
                    <p *ngIf="username.errors?.required">
                        Escoger un nombre de usuario
                    </p>
                    <p *ngIf="username.errors?.usertaken">
                        Este usuario ya existe
                    </p>
                </div>
            </div>

Upvotes: 1

Views: 4831

Answers (2)

Ed05
Ed05

Reputation: 1

Form

 public registerForm: FormGroup;
 constructor(private authService:AuthentificationService,
              private formBuilder: FormBuilder,
              ) { }

this.registerForm = this.formBuilder.group({
  firstname: ['', Validators.required],
  lastname: ['', Validators.required],
  username: ['', [Validators.required,this.validateUsername()]],
  email: ['', [Validators.required, Validators.email]],
  password: ['', [Validators.required, Validators.minLength(6)]],
  confirmPassword: ['', Validators.required]
  }, {
  validator: MustMatch('password', 'confirmPassword')
  }); 

  get f() { return this.registerForm.controls; } // convenience getter for easy access to form fields

Validator

 private validateUsername(): ValidatorFn {
    return (control: AbstractControl) => {
      if (control.value.length != 0) {
        this.authService.checkUsername(control.value).subscribe( data => { 
          if(data) control.setErrors({'alreadyExist': true}) ;
          else return null;
        })
      }else return null;
    }
  }

Html

  <mat-form-field>
          <input matInput placeholder="User Name" required autocomplete="off"  formControlName="username">
          <mat-icon matSuffix>perm_identity</mat-icon>
          <mat-error *ngIf= "f.username.errors?.required">
            Username is required
          </mat-error>
          <mat-error  *ngIf= " f.username.errors?.alreadyExist">
             this username already exists in our database
          </mat-error>
    </mat-form-field>

Upvotes: 0

Here is the example for you

A simple component initializes our reactive form and defines our async validator: validateEmailNotTaken. Notice how form controls in our FormBuilder.group declaration can take async validators as a third argument. Here we’re using only one async validator, but you’d want to wrap multiple async validators in an array:

app.component.ts

import { Component, OnInit } from '@angular/core';
import {
  FormBuilder,
  FormGroup,
  Validators,
  AbstractControl
} from '@angular/forms';

import { SignupService } from './signup.service';

@Component({ ... })
export class AppComponent implements OnInit {
  myForm: FormGroup;

  constructor(
    private fb: FormBuilder,
    private signupService: SignupService
  ) {}

  ngOnInit() {
    this.myForm = this.fb.group({
      name: ['', Validators.required],
      email: [
        '',
        [Validators.required, Validators.email],
        this.validateEmailNotTaken.bind(this)
      ]
    });
  }

  validateEmailNotTaken(control: AbstractControl) {
    return this.signupService.checkEmailNotTaken(control.value).map(res => {
      return res ? null : { emailTaken: true };
    });
  }
}

Our validator is very similar to a typical custom validator. Here we’ve defined our validator in the component class directly instead of a separate file. This makes it easier to access our injected service instance. Notice also how we need bind the this value to insure that it points to the component class.

We could also define our async validator in its own file, for easier reuse and separation of concerns. The only tricky part is to find a way to provide our service instance. Here, for example, we create a class that has a createValidator static method that takes-in our service instance and that returns our validator function:

async-email.validator.ts

import { AbstractControl } from '@angular/forms';
import { SignupService } from '../signup.service';

export class ValidateEmailNotTaken {
  static createValidator(signupService: SignupService) {
    return (control: AbstractControl) => {
      return signupService.checkEmailNotTaken(control.value).map(res => {
        return res ? null : { emailTaken: true };
      });
    };
  }
}

Then, back in our component, we import our ValidateEmailNotTaken class and we can use our validator like this instead:

ngOnInit() {
  this.myForm = this.fb.group({
    name: ['', Validators.required],
    email: [
      '',
      [Validators.required, Validators.email],
      ValidateEmailNotTaken.createValidator(this.signupService)
    ]
  });
}

HTML File

<form [formGroup]="myForm">
  <input type="text" formControlName="name">
  <input type="email" formControlName="email">

  <div *ngIf="myForm.get('email').status === 'PENDING'">
    Checking...
  </div>
  <div *ngIf="myForm.get('email').status === 'VALID'">
    😺 Email is available!
  </div>

  <div *ngIf="myForm.get('email').errors && myForm.get('email').errors.emailTaken">
     this email is already taken!
  </div>
</form>

Upvotes: 4

Related Questions