user1608841
user1608841

Reputation: 2475

Angular async validation not printing error message

Below is my Component :

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { HttpService } from './http.service';
import { ProjectidService } from './projectid.service';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
 projectDetailForm: FormGroup;
  public submitted = false;
  constructor(private fb: FormBuilder, private projectidvalidator: ProjectidService) { }

  ngOnInit() {
    this.projectDetailForm = this.fb.group({
      projectid: ['', [Validators.required], [this.projectidvalidator.validate.bind(this.projectidvalidator)]],
      projectname: ['name', Validators.required]
    })
  }
  get f() { return this.projectDetailForm.controls; }

  get validprojectid() { return this.projectDetailForm.get('projectid'); }

  onSubmit(form: FormGroup) {
    this.submitted = true;

    // stop here if form is invalid
    if (this.projectDetailForm.invalid) {
      return;
    }
    console.log('Valid?', this.projectDetailForm.valid); // true or false
    console.log('ID', this.projectDetailForm.value.projectid);
    console.log('Name', this.projectDetailForm.value.projectname);
  }

}

My Service :

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay, tap, debounceTime } from 'rxjs/operators';
@Injectable()
export class HttpService {

  constructor() { }

  checkProjectID(id): Observable<any> {
     // Here I will have valid HTTP service call to check the data

     return of(true)
  }
}

My Async validator :

import { HttpService } from './http.service';
import { Injectable } from '@angular/core';
import { AsyncValidator, AbstractControl, ValidationErrors } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { map, catchError, debounceTime, switchMap } from 'rxjs/operators';
@Injectable()
export class ProjectidService {

  constructor(private _httpService:HttpService) { }


    validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
        console.log(control.value);

        return control.valueChanges.pipe(
            debounceTime(500),
            switchMap(_ => this._httpService.checkProjectID(control.value).pipe(
                map(isTaken => {
                    console.log(isTaken);
                    if (isTaken) {
                        return { noproject: true }
                    } else {
                        return null
                    }

                })
            )),
            catchError(() => null)
        );


    }

}

and template :

<form [formGroup]="projectDetailForm" name="projectdetails" (ngSubmit)="onSubmit(projectDetailForm)">
    <div class="form-group">
        <label for="id">Project ID</label>
        <input type="text" class="form-control" id="id" [ngClass]="{ 'is-invalid': f.projectid.invalid && (f.projectid.dirty || f.projectid.touched) }" placeholder="Project ID" name="projectid" formControlName='projectid'>
        <button type="button">Validate</button>
        <div *ngIf="f.projectid.invalid && (f.projectid.dirty || f.projectid.touched)" class="invalid-feedback">
            <div *ngIf="f.projectid.errors.required">Project ID is required</div>
            <div *ngIf="f.projectid.errors?.noproject">
                Project id is not valid
            </div>
        </div>
        <div *ngIf="f.projectid.errors?.noproject">
            Project id is not valid
        </div>
        {{f.projectid.errors | json}}
    </div>
    <div class="form-group">
        <label for="name">Project Name</label>
        <input type="text" class="form-control" id="name" placeholder="Project Name" name="projectname" readonly formControlName='projectname'>
    </div>
    <div class="form-group d-flex justify-content-end">
        <div class="">
            <button type="button" class="btn btn-primary">Cancel</button>
            <button type="submit" class="btn btn-primary ml-1">Next</button>
        </div>
    </div>
</form>

Problem is my custom async validation error message is not getting displayed.

Here is stackblitz example

Upvotes: 1

Views: 1106

Answers (3)

wessam yaacob
wessam yaacob

Reputation: 937

The most important point in the async validation is as descriped in Angular Doc

The observable returned must be finite, meaning it must complete at some point. To convert an infinite observable into a finite one, pipe the observable through a filtering operator such as first, last, take, or takeUntil.

so basically you can use for example take(1) , it'll take the first emission then mark the Observable completed

 return control.valueChanges.pipe(
  debounceTime(500),
  take(1),
  switchMap(() =>
    this._httpService.checkProjectID(control.value).pipe(
      map(isTaken =>
        isTaken ? { noproject: true } : null
      )
    ))
)

demo

Upvotes: 0

Hypenate
Hypenate

Reputation: 2064

The real problem is and I have encountered this myself, you subscribe to the value change but you need to wait for the statuschange to return. It is "PENDING" while it is doing the call. The debounce/timer/... are just 'hacks' since you never know when the value is returned.

Declare a variable:

this.formValueAndStatusSubscription: Subscription;

In your

this.formValueAndStatusSubscription =
  combineLatest([this.form.valueChanges, this.form.statusChanges]).subscribe(
    () => this.formStatusBaseOnValueAndStatusChanges = this.form.status
  );

Don't forget to desstroy the subscription

Upvotes: 0

shrys
shrys

Reputation: 5940

You could do it as follows using rxjs/timer:

import { timer } from "rxjs";
....
    return timer(500).pipe(
      switchMap(() => {
        if (!control.value) {
          return of(null);
        }
        return this._httpService.checkProjectID(control.value).pipe(
          map(isTaken => {
            console.log(isTaken);
            if (isTaken) {
              return { noproject: true };
            } else {
              return null;
            }
          })
        );
      })
    );

Sample

Upvotes: 1

Related Questions