2

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?

3 Answers 3

8

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.

Sign up to request clarification or add additional context in comments.

2 Comments

That is the answer that I'm looking for! To others also encounter this issue - AsyncValidatorFn not showing errors, one key point is that the error wouldn't be showed until the returned Observable completed.
That point about completion was really helpful. I always forget that
1

The question is, why do you subscribe and then return that as new Observable? I think you should be able to do this:

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

This way you will let the async validator itself handle subscription for you...

Hope this helps...

2 Comments

catchError expects an observable return value
Sure... I didn't try this, just wrote it of the top of my head... Glad it helped... :)
0

observableOf({ 'isDuplicateEmail': true }) is returning an Observable right? you shoud put normal method in observer.next() not an observable.

try removing the observableOf and test it. it should be something like 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;
          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);
        });
    });

I hope it works this way

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.