skip to Main Content

I have an use case in which I need to verify a few conditions. If the previous one fails I should not verify next one but based on the previous one I should display dialog with title and description indicates what went wrong.
Assuming I have an service (pseudo code)
// verify-condition.service.ts

public validateConditions():Observable<ValidationData> {
  public validateConditions(): Observable<ValidationModal> {
    const hasContract$: Observable<ContractStatus> = this.getContractSignStatus();
    const hasTUTSigned$: Observable<boolean> = this.hasTUTSigned();
    const doData$: Observable<DoModel> = this.getDoData();
    return combineLatest([hasContract$, hasTUTSigned$, doData$]).pipe(
      map(([hasContract, hasTUTSigned, doData,]) => {
        const validationConditions: ValidationModal = {
          conditions: {
            hasContract: hasContract === ContractStatus.SIGNED,
            hasTUTSigned,
            wasSigned: doData.wasSigned,
            valid: doData.valid,
            signDate: doData.signDate,
          }
        };
        return validationConditions;
      })
    );
  }
}

and then in the component I’m using it

 public verifyProcess(): void {
  verifyConditionService.validateConditions().pipe(take(1)).subscribe((validationData:ValidationData=> {
      if (validationData) {
        this.modalValidationService.openModal(validationData);
      } else {
        this.beginProcess();
      }
    })
  }

Thing is that in the service I run all the conditions at once using combineLatest but I would like to run them one by one and if the first fails I should not run the next one but instead throw an error or in some other way indicate that it fails and return data needed to display dialog.

  public validateConditionsInOrder():Observable<ValidationData|boolean> {
    return this.getContractSignStatus().pipe(
        map((signStatus:ContractStatus)=>{
            if(signStatus !== ContractStatus.SIGNED){
                return {
                    hasContract:signStatus
                }
            } else {
                return true;
            }
        }),
        switchMap((previousCondition)=>{ 
            if(!previousCondition){ // so if previous condition failed I DO NOT want to check the next conditions but instead stop verification and return the data from the previous condition

            } else {
                this.hasTUTSigned(); // if the previous condition is OK, I move on to the next one and so on. and if here or the next condition fails I always need to know what went wrong in orther to display dialog based on the info
            }
        })
    )
  }

2

Answers


  1. You can probably create a chain of observables/calls using iif operator. Not the nicest solution, but you can use it as an inspiration and implement a custom pipe, as @Will Alexander has mentioned.

    public validateConditions(): Observable<ValidationModal> {
      // Prepared validateConditions object, so that you partial Observables 
      // have something to work with. Adjust accordingly
      const result = {
        hasContract: false,
        hasTUTSigned: false,
        wasSigned: false,
        valid: null,
        signDate: null,
      };
    
      // Map hasContract state directly and use it later
      const hasContract$: Observable<ContractStatus> =
        this.getContractSignStatus().pipe(
          map((hasContract) => ({
            ...result,
            hasContract: hasContract === ContractStatus.SIGNED,
          }))
        );
    
      const hasTUTSigned$: Observable<boolean> = this.hasTUTSigned();
      const doData$: Observable<DoModel> = this.getDoData();
    
      // Instead of combineLatest, start with piping hasContract$
      return hasContract$.pipe(
        switchMap((validateConditions) =>
          iif(
            //If hasContract is true, chain hasTUTSigned$ and map result
            //Otherwise return previous state of validateConditions
            () => validateConditions.hasContract,
            hasTUTSigned$.pipe(
              map((hasTUTSigned) => ({ ...validateConditions, hasTUTSigned }))
            ),
            of(validateConditions) // Return unchanged data
          )
        ),
        switchMap((validateConditions) =>
          iif(
            // Same as before, but check different condition, map different data
            () => validateConditions.hasTUTSigned,
            doData$.pipe(map((data) => ({ ...validateConditions, ...data }))),
            of(validateConditions) // Return unchanged data
          )
        )
      );
    }
    
    

    Using iif and of you can either chain real observables or pass unmodified validateConditions object if condition weren’t met.

    Login or Signup to reply.
  2. Here’s an example:

    const getCondition = (validity: boolean) => of(validity).pipe(delay(1000));
    
    const condition1$ = getCondition(true);
    const condition2$ = getCondition(true);
    const condition3$ = getCondition(true);
    
    const errorIfFalseCondition =
      (error: string) =>
      <T>(obs$: Observable<T>): Observable<T> =>
        obs$.pipe(
          tap((x) => {
            if (!x) {
              throw new Error(error);
            }
          })
        );
    
    concat(
      condition1$.pipe(errorIfFalseCondition(`Condition 1 was not respected`)),
      condition2$.pipe(errorIfFalseCondition(`Condition 2 was not respected`)),
      condition3$.pipe(errorIfFalseCondition(`Condition 3 was not respected`))
    )
      .pipe(
        tap(() => console.log(`[DEBUG] A condition has passed`)),
        ignoreElements(),
        tap({ complete: () => console.log(`All conditions passed`) })
      )
      .subscribe();
    

    And a live demo on Stackblitz where you can toggle the conditions to return false when you want to see the different outputs.

    The conditions are ran one after another, sequentially. If one fails (throws), it’ll kill the queue and you’ll get the error that you defined. So you could downstream easily rely on the error message to display something in your modal.

    Have a play and see that at the moment with all conditions set to true, after 3s, you get the message "All conditions passed" but if you set one to false you’ll get an error and it’ll stop.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search