0

I am in a Angular proyect and I am using a reactive form. What I am trying to do is add my own validator. To do this, I need to check if the id exists in the database. For this reason I use an asynchronous validator. In case the id is already being used by another element, then it must return an error. Otherwise, the form will be valid.

This is the validator.

    export class ValidadorOperationId {
      static createValidatorOperationId(name, operationListService: OperationsListService, time: number = 500): AsyncValidatorFn {
        return (control: AbstractControl): Observable<ValidationErrors> => {
          return timer(time).pipe(
            switchMap(() => operationListService.getOperationsByUuid(name, control.value)),
            map(res => {
              return res ? { operationIdExist: true } : null
              }
            )
          );
        };
      }
    }

And this is how I call to validator in my component.

 this.fb.group({
   value: [this.operationDetails['operationid'], [Validators.required], ValidadorOperationId.createValidatorOperationId(this.nombre,this.operationListService)]
 })

The problem is that the control has status: pending and I do not know what is wrong.

If you need some more code, just ask me.

Can anyone help me?

  • Angular Version: 8.2.14
  • Rxjs version: 6.5.3

UPDATE 22/10/2020

I have checked that the status:pending only appears when the service does not return anything (error 404)

Alba
  • 422
  • 2
  • 9
  • 20
  • Shouldn't the async validator also be added within an array? `value: [this.operationDetails['operationid'], [Validators.required], [ValidadorOperationId.createValidatorOperationId(this.nombre,this.operationListService)]]` – MoxxiManagarm Oct 21 '20 at 11:51
  • The result is the same. The status continues being pending. @MoxxiManagarm – Alba Oct 21 '20 at 11:56
  • Can you maybe create a stackblitz which shows the same behavior? – MoxxiManagarm Oct 21 '20 at 12:00
  • Does this answer your question? [Angular 4: reactive form control is stuck in pending state with a custom async validator](https://stackoverflow.com/questions/48655324/angular-4-reactive-form-control-is-stuck-in-pending-state-with-a-custom-async-v) – Akash Oct 21 '20 at 12:50
  • I think, in order to work, your observable should complete. Is your `getOperationsByUuid` method complete ? add a `take(1)` after your map operator and see if it resolves. – Quentin Fonck Oct 21 '20 at 12:54
  • Here's an alternative that may help. A three part series on using ngmodel https://dev.to/jwp/angular-ngmodel-model-and-viewmodel-5m – JWP Oct 21 '20 at 13:50
  • I did a stackblitz demo: And it should works. https://stackblitz.com/edit/angular-ivy-cfpggq?file=src%2Fapp%2Fapp.component.ts Please review ```getOperationsByUuid``` if it's completed. – Luis Reinoso Oct 21 '20 at 14:38
  • @Akash I have tried to add .pipe(first()) after first pipe but it still doesn't work. Am I doing it right?. It would be something like this: return timer(time).pipe('rest code').pipe(first()) – Alba Oct 22 '20 at 07:57
  • @QuentinFonck Is there a way for me to complete all the observables and not just the first one? – Alba Oct 22 '20 at 08:04
  • @Akash I have change my code like the first answer of your link but when I get error 404, the state is pending – Alba Oct 22 '20 at 08:45
  • @Alba well you can complete the entire stream by adding the `take(1)` or the `first()` at the very end of your pipe operator. – Quentin Fonck Oct 22 '20 at 09:14

2 Answers2

2

Make sure that your getOperationsByUuid return at least an observable or a promise even when you get a 404 error. That might be the reason why it get stuck in pending state because this function doesn't return anything, so your observable doesn't emit.
Notice the tap operator. If this log doesn't get print, that's the reason it stays in pending, because it never resolves.

    export class ValidadorOperationId {
      static createValidatorOperationId(name, operationListService: OperationsListService, time: number = 500): AsyncValidatorFn {
        return (control: AbstractControl): Observable<ValidationErrors> => {
          return timer(time).pipe(
            switchMap(() => operationListService.getOperationsByUuid(name, control.value)),
            tap(res => console.log('this should print')),
            map(res => {
              return res ? { operationIdExist: true } : null
              }
            ),
            take(1) // not useful if getOperationsByUuid is an http call (http calls always complete)
          );
        };
      }
    }
Quentin Fonck
  • 1,286
  • 9
  • 14
  • Yes, you are right. When my service return 404 error, the message doesn't print. How resolve this? Can you help me? Sorry for the inconvenience – Alba Oct 22 '20 at 09:35
  • I have done is adding the catchError operator after map operator and return an observable, but I dont know if this is the best practice. – Alba Oct 22 '20 at 09:38
  • Well, I would catch and treat this error in the `getOperationsByUUid()` since it is related to that call. Because If you have any other error in your code other than that 404 error it will return something which can lead to unwanted side effect. Try to solve why you have a 404 error in the first place. – Quentin Fonck Oct 22 '20 at 11:51
1

To handle 404 or any other http error, you can you RxJS's catchError operator. Below is how your validator will look like -

export class ValidadorOperationId {
      static createValidatorOperationId(name, operationListService: OperationsListService, time: number = 500): AsyncValidatorFn {
        return (control: AbstractControl): Observable<ValidationErrors> => {
          return timer(time).pipe(
            switchMap(() => operationListService.getOperationsByUuid(name, control.value)
           .pipe(catchError(err => of(null))), // <-- this is what you need
            map(res => {
              return res ? { operationIdExist: true } : null
              }
            )
          );
        };
      }
    }
Akash
  • 4,412
  • 4
  • 30
  • 48