Http Hobo

Another blog with web-development tips’n’tricks. | About me

2 posts tagged

angular

Angular 2+ async validation getting stuck in PENDING state?

Hey all,

Haven’t been having anything really interesting to cover here until today. I was implementing async validation in an Angular 7 application, and got stuck with it not really working.

My observations:

  • Request to the server is made, and my AsyncValidationFn gets executed, it returns an Observable successfully.
  • Map operator used with the observable works correctly.
  • Form control doesn’t get any errors

I investigated further and found out that form control status gets stuck in PENDING state. I googled a bit and realized that Observable is not completing, that’s why. I was a bit pissed, why this wasn’t covered in the documentation, but then it was:

Just like synchronous validators have the ValidatorFn and Validator interfaces, asynchronous validators have their own counterparts: AsyncValidatorFn and AsyncValidator.

They are very similar with the only difference being:
* They must return a Promise or an Observable,
* <strong>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.</strong>
* It is important to note that the asynchronous validation happens after the synchronous validation, and is performed only if the synchronous validation is successful. This check allows forms to avoid potentially expensive async validation processes such as an HTTP request if more basic validation methods fail.

However, it’s weird that their example misses using any of the “filtering” operators, that was the culprit. Fixed my code, and everything now works like a charm.

Was:

export function emailUniqueValidator(jsonRpc: JsonRpcService, auth: AuthService): AsyncValidatorFn {
    return (control: AbstractControl) => {
        return jsonRpc.sendRequest<Boolean>('api1.user.is-email-taken', {
            email: control.value,
            user_id: auth.identity.id
        }, {
            headers: auth.getAuthorizationHeader()
        }).pipe(
            map(isTaken => {
                return isTaken ? {emailTaken: true} : null
            }),
            catchError(() => {
                return null;
            })
        );
    }
}

Is:

export function emailUniqueValidator(jsonRpc: JsonRpcService, auth: AuthService): AsyncValidatorFn {
    return (control: AbstractControl) => {
        return jsonRpc.sendRequest<Boolean>('api1.user.is-email-taken', {
            email: control.value,
            user_id: auth.identity.id
        }, {
            headers: auth.getAuthorizationHeader()
        }).pipe(
            map(isTaken => {
                return isTaken ? {emailTaken: true} : null
            }),
            catchError(() => {
                return null;
            }),
            first()
        );
    }
}

Best regards,
George

2019   angular   async validation

Angular 2+ service doesn’t feel being shared across components?

Probably the reason is that you’ve declared it as a component provider, not app provider. Shared service has to be declared in app.component.ts in “providers” section:

@NgModule({
  declarations: [
    // Some declarations
  ],
  imports: [
    // Some imports
    RouterModule.forRoot(
        appRoutes,
        { enableTracing: true }
    )
  ],
  providers: [
      YourService,
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

If you declare it in component declaration like this:

@Component({
    selector: 'my-component',
    templateUrl: './templates/my-component.component.html',
    providers: [MyService],
})

then a fresh instance of a service will be created, instead of using a app-wide one.

If you need a service instance shared across components, declare it app-wide. If you need a distinct service instance for a component, declare it as a component’s provider.

2017   angular