Search code examples
angularrxjsangular-formsangular-observable

Angular Form custom validator http observable returning null on http call


I am trying to setup a form field which will check if a email exists. I have looked at a few examples and have got the validation working, but when I implement the http pipe, map to my services' Observable, it throws it as null? I can assume I am piping to it incorrectly from my service but I am not too sure.

Is anyone able to help me?

core.js:6014 ERROR TypeError: Cannot read property 'emailService' of undefined

app.component.ts

export class AppComponent implements OnInit {

  signUpForm: FormGroup;

  constructor(private fb: FormBuilder, private emailService: EmailService) { }

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

  //Validator for checking if email name is taken or not
  validateEmailNotTaken(control: AbstractControl): Observable<{ [key: string]: any } | null> {

      if (control.value === null || control.value.length === 0) {
        return of(null);
      }
      else {
        return timer(1000).pipe(
          switchMap(() => {


            this.emailService.checkEmailExists(control.value).pipe(
              map(res => {
                //Do what with response
                console.log(res);

                if (!res) {
                  return { taken: true };
                }

                return of(null);
              })
            );


          })
        );
      }


  }

}

email.service.ts

 interface IServerCheckEmailExists {
    "available": boolean;
}
export interface ICheckEmailExists {
    taken: boolean;
}

@Injectable({ providedIn: 'root' })
export class EmailService {

    constructor(private _http: HttpClient) {
    }

    checkEmailExists(email: string): Observable<ICheckEmailExists[]> {

        var postObject = {"action": "checkEmailExists"};
        return this._http.post<IServerCheckEmailExists[]>("myapiurl/" + email, postObject).pipe(
            map(o => o.map((sp): ICheckEmailExists => ({
                taken: sp.available
            })))
        );
    }
}

Solution

  • The only issue that I see in your code is that you're not returning the call to this.emailService.checkEmailExists(control.value) inside the switchMap which you should be doing since you are using {}. Something like this:

    import { Component, OnInit } from "@angular/core";
    import {
      FormGroup,
      FormBuilder,
      Validators,
      AbstractControl
    } from "@angular/forms";
    import { Observable, of, timer } from "rxjs";
    import { switchMap, map } from "rxjs/operators";
    
    import { EmailService } from "./email.service";
    
    @Component({
      selector: "my-app",
      templateUrl: `./app.component.html`,
      styleUrls: [`./app.component.css`]
    })
    export class AppComponent implements OnInit {
      signUpForm: FormGroup;
    
      constructor(private fb: FormBuilder, private emailService: EmailService) {}
    
      ngOnInit() {
        this.signUpForm = this.fb.group({
          name: ["", Validators.required],
          email: ["", [Validators.required], [this.validateEmailNotTaken.bind(this)]]
        });
      }
    
      //Validator for checking if email name is taken or not
      validateEmailNotTaken(
        control: AbstractControl
      ): Observable<{ [key: string]: any } | null> {
        if (control.value === null || control.value.length === 0) {
          return of(null);
        } else {
          return timer(1000).pipe(
            switchMap(() => {
              // RIGHT HERE 👇🏼
              return this.emailService.checkEmailExists(control.value).pipe(
                map(res => {
                  //Do what with response
                  console.log(res);
                  if (res) {
                    return of(null);
                  }
                  return { taken: true };
                })
              );
            })
          );
        }
      }
    }
    

    Here's a Working Code Example for your ref.