1

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

3 Answers 3

1

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

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

4 Comments

shreyas why its not working with debouceTime and using control.valueChange ? any specific reason for the same ?
I think your approach coincides with this explanation
When the call takes longer then 500 miliseconds this code will not work as intended.
0

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

Comments

0

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

2 Comments

any way to achieve debounceTime and distinctUntilChange ?
I'll take a look

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.