Search code examples
javascriptangulartypescriptrxjsobservable

Angular verify conditions one after another


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
            }
        })
    )
  }


Solution

  • 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.