Search code examples
angularrxjsangular-forms

Angular custom validator is not throwing an error when using subscribe in it


I am trying to create custom validator which needs to hit two endpoint to fetch needed data and then compare properties of the data and if they are not equal then I want to throw an error. Here is the validator.

 checkIfSalaIsFull(control: AbstractControl): {[salaIsFull: string] : boolean}{
    if(control.value !=null){
      this.salaService.findActiveCards(control.value).subscribe(karty=>{
        this.salaService.findSalaByNRSali(control.value).subscribe(sala =>{
          if(karty.length === sala.pojemnosc){
            console.log(karty.length);
            console.log(sala.pojemnosc);
            return {'salaIsFull' : true};
          }
        });
      });
    }
    return null;
  }

The problem is that error is not thrown even though console.log show the same value for showed properties.

<div class="form-group">
    <label for="nr_sali">Numer sali</label>
    <select class="form-control" id="nr_sali" formControlName="nr_sali">
      <option *ngFor="let sala of sale" [value]="sala.nr_sali" >{{sala.nr_sali}} - {{sala.oddzial}}</option>

    </select>

    <span *ngIf="formAddKarta.get('nr_sali').errors?.salaIsFull"
          class="help-block text-danger">
          Sala jest pełna
    </span>
</div>

My backend is working, I checked it with postman.

Here is also a setup of FormGroup which I am using:

 this.formAddKarta = new FormGroup({
      data_przyjecia: new FormControl(null, [KartyListaComponent.checkIfDateIsLessThanToday.bind(this),
        Validators.required]),
      godzina_przyjecia: new FormControl(null,[Validators.required]),
      data_wypisu: new FormControl(null),
      nr_sali: new FormControl(null, [Validators.required,this.checkIfSalaIsFull.bind(this)]),
      pesel: new FormControl(this.pesel)
    });

I set this custom validator in Validators table in nr_sali FormControl.

EDIT:

I tried to use map instead of subscribe but then it is not sending request to retrieve sala, only sending request to retreive karty.

 checkIfSalaIsFull(control: AbstractControl): {[salaIsFull: string] : boolean}{
    if(control.value !=null){
      this.salaService.findActiveCards(control.value).subscribe(karty=>{
        this.salaService.findSalaByNRSali(control.value).pipe(map(sala =>{
          if(karty.length === sala.pojemnosc){
            console.log(karty.length);
            console.log(sala.pojemnosc);
            return {'salaIsFull' : true};
          }else{
            return null;
          }
        }));
      });
    }else{
      return null;
    }

  }

Solution

    1. you need to use async validator instead of validator, because you need to send request, and you need to put your function as third parameter in your formControl (as Async validator)
    2. your validator function should be return Observable<ValidationErrors | null> | Promise<ValidationErrors | null>
    3. you need to use switchMap and map instead of subscription

    here is the code example

    imports

    import { map, switchMap } from "rxjs/operators";
    import { Observable } from "rxjs/internal/observable";
    

    async validator example

      checkIfSalaIsFull(
        control: AbstractControl
      ): Observable<ValidationErrors | null> {
        if (control.value != null) {
          return this.salaService.findActiveCards(control.value).pipe(
            switchMap(karty => {
              return this.salaService.findSalaByNRSali(control.value).pipe(
                map(sala => {
                    console.log(karty.length);
                    console.log(sala.pojemnosc);
                    return karty.length === sala.pojemnosc ? { salaIsFull: true } : null;
    
                })
              );
            })
          );
        }
        return null;
      }
    

    Form Implementation example

     this.formAddKarta = new FormGroup({
          data_przyjecia: new FormControl(null, [
    KartyListaComponent.checkIfDateIsLessThanToday.bind(this),  /** <-- if this function send the request you need to change this one too */
    
            Validators.required]),
          godzina_przyjecia: new FormControl(null, [Validators.required] ),
          data_wypisu: new FormControl(null),
          nr_sali: new FormControl(null, 
    [Validators.required],
     [this.checkIfSalaIsFull.bind(this)]),  /** <-- third argument of formControl is the async validators array */
          pesel: new FormControl(this.pesel)
        });