Search code examples
angular8angular-reactive-formsangular-validation

Angular 8 FormGroup async validation from API call


I need to validate Unique product title, except the current product id.

Please note that I need to validate FormGroup using async validation - API service call.

The function contains hardcoded value 2, please ignore it.

product-component.ts

ngOnInit() {
    this.productForm = this.fb.group({
      categoryId: ['', [Validators.required]],
      title: ['', [Validators.required]],
      description: [''],
      oldPrice: ['', [Validators.required, Validators.maxLength(10), Validators.pattern('^\\d+$')]],
      newPrice: ['', [Validators.required, Validators.maxLength(10), Validators.pattern('^\\d+$')]]
}, 
// {validators: ValidateUniqueProductTitle.checkProductTitle(this.productService, 2)}
{asyncValidators: [ValidateUniqueProductTitle.checkProductTitle(this.productService, 2)]}
);

Validate class:

export class ValidateUniqueProductTitle {

public static checkProductTitle(productService: ProductService, productId) {
    return (group: AbstractControl): Observable<ValidationErrors | null> => {
        return productService.checkExisting(group.controls['title'].value, productId)
                            .pipe(
                                debounceTime(200),
                                distinctUntilChanged(),
                                take(1),
                                map((data: boolean) => {
                                    console.log('checkExisting api called', data);
                                    return data ?  {title_exists: true} : null;
                                })
                            ); 
        }
}

Issue:

Validate class function is never called when I use this line:

{asyncValidators:[ValidateUniqueProductTitle.checkProductTitle(this.productService, 2)]}

When I use this line, function gets called but API is not fired:

{validators: ValidateUniqueProductTitle.checkProductTitle(this.productService, 2)}


Solution

  • It was a silly mistake. Should have used FormGroup instead of Abstract Control type in return.

    Validate class:

    export class ValidateUniqueProductTitle {
    
      public static checkProductTitle(productService: ProductService, productId) {
           return (group: AbstractControl): Observable<ValidationErrors | null> => {
                return productService.checkExisting(group.controls['title'].value, productId)
                            .pipe(
                                debounceTime(200),
                                distinctUntilChanged(),
                                take(1),
                                map((data: boolean) => {
                                    console.log('checkExisting api called', data);
                                    return data ?  {title_exists: true} : null;
                                })
                            ); 
        }
    } 
    

    Also, it's important to note that:

    Async validators are fired only when all sync validators return null.

    I was expecting all validation errors (sync and async) to appear together, but that doesn't happen.