Angular逐个验证条件。

huangapple go评论66阅读模式
英文:

Angular verify conditions one after another

问题

以下是您要翻译的代码部分:

我有一个用例,需要验证一些条件。如果前一个条件失败,我不应该验证下一个条件,而应该根据前一个条件显示一个带有标题和描述的对话框,指示出了什么问题。

假设我有一个服务(伪代码)
// verify-condition.service.ts
```typescript
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;
    })
  );
}

然后在组件中使用它

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

事实上,在服务中,我一次运行所有条件,但我想逐个运行它们,如果第一个失败,我不应该运行下一个,而是抛出错误或以其他方式指示它失败,并返回显示对话框所需的数据。

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){ // 如果前一个条件失败,我不想检查下一个条件,而是停止验证并返回前一个条件的数据

          } else {
              this.hasTUTSigned(); // 如果前一个条件OK,我继续下一个条件,如果这里或下一个条件失败,我始终需要知道出了什么问题,以便基于信息显示对话框
          }
      })
  )
}

希望这能帮助您!如果您需要进一步的帮助,请随时提问。

英文:

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&lt;ValidationData&gt; {
  public validateConditions(): Observable&lt;ValidationModal&gt; {
    const hasContract$: Observable&lt;ContractStatus&gt; = this.getContractSignStatus();
    const hasTUTSigned$: Observable&lt;boolean&gt; = this.hasTUTSigned();
    const doData$: Observable&lt;DoModel&gt; = this.getDoData();
    return combineLatest([hasContract$, hasTUTSigned$, doData$]).pipe(
      map(([hasContract, hasTUTSigned, doData,]) =&gt; {
        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=&gt; {
      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&lt;ValidationData|boolean&gt; {
    return this.getContractSignStatus().pipe(
        map((signStatus:ContractStatus)=&gt;{
            if(signStatus !== ContractStatus.SIGNED){
                return {
                    hasContract:signStatus
                }
            } else {
                return true;
            }
        }),
        switchMap((previousCondition)=&gt;{ 
            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
            }
        })
    )
  }

答案1

得分: 0

你可以使用 iif 运算符创建一系列的 observables/calls 链。这不是最优雅的解决方案,但你可以将其作为灵感,实现一个自定义管道,就像 @Will Alexander 提到的那样。

public validateConditions(): Observable&lt;ValidationModal&gt; {
  // 准备了 validateConditions 对象,以便你的部分 Observables 有东西可以使用。根据需要进行调整
  const result = {
    hasContract: false,
    hasTUTSigned: false,
    wasSigned: false,
    valid: null,
    signDate: null,
  };

  // 直接映射 hasContract 状态并稍后使用
  const hasContract$: Observable&lt;ContractStatus&gt; =
    this.getContractSignStatus().pipe(
      map((hasContract) =&gt; ({
        ...result,
        hasContract: hasContract === ContractStatus.SIGNED,
      }))
    );

  const hasTUTSigned$: Observable&lt;boolean&gt; = this.hasTUTSigned();
  const doData$: Observable&lt;DoModel&gt; = this.getDoData();

  // 与使用 combineLatest 不同,从管道化的 hasContract$ 开始
  return hasContract$.pipe(
    switchMap((validateConditions) =&gt;
      iif(
        // 如果 hasContract 为 true,链式处理 hasTUTSigned$ 并映射结果
        // 否则返回 validateConditions 的先前状态
        () =&gt; validateConditions.hasContract,
        hasTUTSigned$.pipe(
          map((hasTUTSigned) =&gt; ({ ...validateConditions, hasTUTSigned }))
        ),
        of(validateConditions) // 返回不变的数据
      )
    ),
    switchMap((validateConditions) =&gt;
      iif(
        // 与之前相同,但检查不同的条件,映射不同的数据
        () =&gt; validateConditions.hasTUTSigned,
        doData$.pipe(map((data) =&gt; ({ ...validateConditions, ...data }))),
        of(validateConditions) // 返回不变的数据
      )
    )
  );
}

使用 iifof,你可以要么链式处理真实的 observables,要么在条件不满足时传递未修改的 validateConditions 对象。

英文:

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&lt;ValidationModal&gt; {
  // 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&lt;ContractStatus&gt; =
    this.getContractSignStatus().pipe(
      map((hasContract) =&gt; ({
        ...result,
        hasContract: hasContract === ContractStatus.SIGNED,
      }))
    );

  const hasTUTSigned$: Observable&lt;boolean&gt; = this.hasTUTSigned();
  const doData$: Observable&lt;DoModel&gt; = this.getDoData();

  // Instead of combineLatest, start with piping hasContract$
  return hasContract$.pipe(
    switchMap((validateConditions) =&gt;
      iif(
        //If hasContract is true, chain hasTUTSigned$ and map result
        //Otherwise return previous state of validateConditions
        () =&gt; validateConditions.hasContract,
        hasTUTSigned$.pipe(
          map((hasTUTSigned) =&gt; ({ ...validateConditions, hasTUTSigned }))
        ),
        of(validateConditions) // Return unchanged data
      )
    ),
    switchMap((validateConditions) =&gt;
      iif(
        // Same as before, but check different condition, map different data
        () =&gt; validateConditions.hasTUTSigned,
        doData$.pipe(map((data) =&gt; ({ ...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.

答案2

得分: 0

这里是一个示例:

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(`条件1未被满足`)),
  condition2$.pipe(errorIfFalseCondition(`条件2未被满足`)),
  condition3$.pipe(errorIfFalseCondition(`条件3未被满足`))
)
  .pipe(
    tap(() => console.log(`[DEBUG] 一个条件已通过`)),
    ignoreElements(),
    tap({ complete: () => console.log(`所有条件都已通过`) })
  )
  .subscribe();

Stackblitz 上的实时演示 中,您可以切换条件以返回 false,从而查看不同的输出。

这些条件依次运行,如果其中一个失败(抛出异常),它将终止队列并显示您定义的错误消息。因此,您可以轻松地依赖错误消息来在您的模态框中显示某些内容。

玩一下,看看如果将所有条件都设置为 true,在3秒后,您将收到消息 "所有条件都已通过",但如果将其中一个设置为 false,您将收到一个错误并停止执行。

英文:

Here's an example:

const getCondition = (validity: boolean) =&gt; of(validity).pipe(delay(1000));

const condition1$ = getCondition(true);
const condition2$ = getCondition(true);
const condition3$ = getCondition(true);

const errorIfFalseCondition =
  (error: string) =&gt;
  &lt;T&gt;(obs$: Observable&lt;T&gt;): Observable&lt;T&gt; =&gt;
    obs$.pipe(
      tap((x) =&gt; {
        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(() =&gt; console.log(`[DEBUG] A condition has passed`)),
    ignoreElements(),
    tap({ complete: () =&gt; 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.

答案3

得分: 0

public validateConditions() {
    const hasContract$: Observable<ContractStatus> = this.getContractSignStatus();
    const hasTUTSigned$: Observable<boolean> = this.hasTUTSigned();
    const doData$: Observable<DoModel> = this.getDoData();
    return new Observable(subscriber => {
        const condi = {
            hasContract: null,
            hasTUTSigned: null,
            wasSigned: null,
            valid: null,
            signDate: null,
        }

        concat(
            hasContract$.pipe(tap(hasContract => condi.hasContract = hasContract === ContractStatus.SIGNED)),
            hasTUTSigned$.pipe(tap(hasTUTSigned => condi.hasTUTSigned = hasTUTSigned)),
            doData$.pipe(tap(doData => Object.assign(condi, {
                wasSigned: doData.wasSigned,
                valid: doData.valid,
                signDate: doData.signDate,
            }))),
        ).subscribe({
            complete: () => subscriber.next(condi),
            error: error => {
                console.error(error); // handle error
                subscriber.next(condi);
            }
        })
    }).pipe(take(1), map(condi => ({ conditions: condi, }) as ValidationModal))
}

行为:

  • 后续的 Observable 将等待前一个 Observable 完成
  • 当 Observable 收到响应时,使用 tap 操作符更新其相应的验证属性
  • 当所有 3 个 Observable 完成时,返回验证状态
  • 如果 Observable 遇到错误,链中的所有 Observable 将不会触发
  • 当发生错误时,触发错误处理程序,并根据已修改的条件返回条件
  • 一旦条件最终确定,将其映射为 ValidationModal 的结构

注意:
我的方法在很大程度上依赖于 Observable 的完成,并且不适用于实际的流。


<details>
<summary>英文:</summary>

```typescript
public validateConditions () {
    const hasContract$: Observable&lt;ContractStatus&gt; = this.getContractSignStatus();
    const hasTUTSigned$: Observable&lt;boolean&gt; = this.hasTUTSigned();
    const doData$: Observable&lt;DoModel&gt; = this.getDoData();
    return new Observable(subscriber =&gt; {
        const condi = {
            hasContract : null,
            hasTUTSigned: null,
            wasSigned   : null,
            valid       : null,
            signDate    : null,
        }
        
        concat(
            hasContract$.pipe(tap(hasContract =&gt; condi.hasContract = hasContract === ContractStatus.SIGNED)), 
            hasTUTSigned$.pipe(tap(hasTUTSigned =&gt; condi.hasTUTSigned = hasTUTSigned)),
            doData$.pipe(tap(doData =&gt; Object.assign(condi, {
                wasSigned : doData.wasSigned,
                valid     : doData.valid,
                signDate  : doData.signDate,
            }))), 
        ).subscribe({
            complete    : ()    =&gt; subscriber.next(condi),
            error       : error =&gt; {
                console.error(error);       // handle error
                subscriber.next(condi);     
            }
        })
    }).pipe(take(1), map(condi =&gt; ({conditions  : condi,}) as ValidationModal))
}

the base of my method relies on rxjs.concat to chain and control sequence of each observable, then translate the data by creating a new Observable Class

behaviour:

  • the later observable will wait until the previous observable is completed
  • when an observable receive reply, update its respective validation property using tap operator
  • when all 3 observable has completed, return the validation status
  • if an observable encounter error, all observable under the chain will not trigger
  • when an error occur, triggers the error handler, and return the condition base on however much was modified
  • once condition is finalized, map its structure for ValidationModal

Caveat:
my method relies heavily on the observable to COMPLETE and does not works with actual streams

huangapple
  • 本文由 发表于 2023年7月11日 14:37:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76659250.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定