Search code examples
angularobservableangular-custom-validators

Angular Custom Async Validator Not Showing Error


My async validator is not working when I actually make an async call (but works when I mock it with Observable.of(result). The html looks like this:

   <div class="form-group">
          <label for="emailAddress" class="control-label">Email</label>
          <input type="email"
                 id="emailAddress"
                 class="form-control"
                 [(ngModel)]="parent.EmailAddress"
                 name="emailAddress"
                 [ngModelOptions]="{updateOn: 'blur'}"
                 appDuplicateEmailValidator
                 #emailAddress = "ngModel">
        </div>

The validator in question is appDuplicateEmailValidator.

The validation code looks like this:

  public validate(control: AbstractControl): Observable<ValidationErrors | null> {
    // return observableOf({ 'isDuplicateEmail': true });

    return this.checkForDupeUserAndGetValidationState(emailAddress);
  }

  private checkForDupeUserAndGetValidationState(emailAddress): Observable<ValidationErrors | null> {
    const validationStateObs: Observable<ValidationErrors | null> = new Observable(observer => {
      this.adminService.getUser(emailAddress).subscribe(
        (res: any) => {
          const validationState = !!res ? observableOf({ 'isDuplicateEmail': true }) : null;
          console.log('Observer next, validation state: ', validationState); // Gets correct val state
          observer.next(validationState);
          console.log('Is it hitting this'); // Gets hit
        }, (err) => {
          observer.next(null);
        });
    });

An important note is that when I uncomment // return observableOf({ 'isDuplicateEmail': true });, everything works as expected. But the way you see the code above, it doesn't work, even though the observable is returning the correct value (logged it an used debugger). By "it doesn't work" I mean that the form control is never entering an error state, as shown by:

<pre>IS INVALID: {{emailAddress.invalid}}</pre>
<pre>ERRORS: {{emailAddress.errors | json}}</pre>

Why and how do I fix it?


Solution

  • you have 3 issues. 1. you need to actually return your observable 2. you're sending an observable instead of a value through your observer 3. the observable needs to complete.

    you could do this:

      private checkForDupeUserAndGetValidationState(emailAddress): Observable<ValidationErrors | null> {
        const validationStateObs: Observable<ValidationErrors | null> = new Observable(observer => {
          this.adminService.getUser(emailAddress).subscribe(
            (res: any) => {
              const validationState = !!res ? { 'isDuplicateEmail': true } : null; // just send the value
              observer.next(validationState);
              observer.complete(); //complete here
            }, (err) => {
              observer.next(null);
              observer.complete(); // and complete here
            });
        });
        return validationStateObs; // return it here
      }
    

    but way easier and cleaner would just be to return the response from your service and using operators instead of creating an observable and nesting a subscribe inside it..

      private checkForDupeUserAndGetValidationState(emailAddress): Observable<ValidationErrors | null> {
        return this.adminService.getUser(emailAddress).pipe(
          map((res: any) => !!res ? { 'isDuplicateEmail': true } : null),
          catchError((err) => observableOf(null))
        )
      }
    

    http observables naturally complete after one emission, so you don't need to worry about completing or any of the other complex logic you had.