0
votes

I build a standard register form with Angular and I want to validate if the email is already taken with an async directive (UserExistsDirective), then manage the error message with a second directive (ValidityStyleDirective). The second directive is used on other inputs as well.

ValidityStyleDirective needs to wait until UserExistsDirective completed before execute. Otherwise, the error message will be out of sync.

Here the code I've done so far, I struggle to find the right solution so I need help! Again, UserExistsDirective needs to complete before goto ValidityStyleDirective. How can I do that with Angular ?

Thank you,

HTML :

...
<div class="mb-4 inputcontainer">
                    <label class="form-label" for="email">Courriel</label>
                    <input appUserExists validityStyle formControlName="email" class="form-control" id="email" type="email" name="email" >
                    <div *ngIf="registerForm.get('email')?.pending" class="icon-container">
                        <i class="loader"></i>
                    </div>
                    <div class="valid-feedback">Looks good!</div>
                    <div class="invalid-feedback">
                        <span *ngIf="registerForm.get('email')?.hasError('required')">Please provide an email.</span>
                        <span *ngIf="registerForm.get('email')?.errors?.['pattern']">Please provide a valid email.</span>
                        <span *ngIf="registerForm.get('email')?.errors?.['userExists']">This email address is already registered. Please use another one.</span>
                    </div>
                </div>
...

UserExistsDirective : it add a the key userExists to validator if an email is found.

@Directive({
  selector: '[appUserExists]',
  providers: [{    
    provide: NG_ASYNC_VALIDATORS,    
    useExisting: UserExistsDirective,    
    multi: true  
  }]
})
export class UserExistsDirective implements AsyncValidator {
  constructor() { 

  }
  validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
    return this.emailExists(control.value).pipe(
      take(1),
      map(isTaken => (isTaken ? { userExists: true } : null)),
      catchError(() => of(null))
    );
  }

  emailExists(email: string): Observable<boolean> {
    return of(email).pipe(
      delay(500),
      map((email) => {
        const emails = ['[email protected]', '[email protected]'];
        return emails.includes(email);
      })
    );
  }
}

Then ValidityStyleDirective add the error CSS class if needed

@Directive({
    selector: '[validityStyle]'
})
export class ValidityStyleDirective {

    constructor(@Self() private ngControl: NgControl,
        private formGroup: FormGroupDirective,
        private renderer: Renderer2,
        private el: ElementRef) { }
    
    @HostListener('blur')
    onBlur($event: any) {
        if (this.ngControl.errors == null) {
            this.formGroup.control.get('email')?.setErrors(null);
        }

        if (this.ngControl.value === '') {
            this.renderer.removeClass(this.el.nativeElement, 'is-invalid');
            return false;
        }

        if (!this.ngControl.valid) {
            this.renderer.addClass(this.el.nativeElement, 'is-invalid');
            return true;
        }

        this.renderer.removeClass(this.el.nativeElement, 'is-invalid');
        return false;
    }
}