使用来自对话框的输入订阅可观察的异步地图结果,使用来自地图的结果来路由

Subscribe to observable, async map result with input from dialog, use result from map to route

我正在调用一个 API 服务,其中 returns 一个 Observable - 包含一个元素数组。

apiMethod(input: Input): Observable<ResultElement[]>

由此我一直在选择数组的第一个元素,并订阅它。然后使用该元素路由到另一个页面,如下所示:

this.apiService
  .apiMethod(input)
  .pipe(map((results) => results[0])
  .subscribe(
    (result) => {
      return this.router.navigate('elements/', result.id)
  }
)

这很好用。

问题是,我不想只使用第一个元素,我想要一个 MatDialog 或其他类似的弹出窗口,让用户选择选择哪个元素,然后路由到正确的那个。

如果列表仅包含一个元素,则不应显示对话框,并且应立即路由用户。

我试图在 .pipe(map()) 函数中打开一个对话框,但是 subscribe() 在我得到用户的回答之前发生,导致它失败。而且我不确定这是否是正确的方法。你们会如何解决这个问题?

编辑 最终完成了@BizzyBob 建议的部分工作:

在 API 调用中将地图更改为 switchmap,以这种方式进行:

this.apiService
  .apiMethod(input)
  .pipe(switchMap((results) => this.mapToSingle(results)
  .subscribe(
    (result) => {
      return this.router.navigate('elements/', result.id)
  }
)

mapToSingle(ResultElement[]) 是这样的:

private mapToSingle(results: ResultElement[]): Observable<ResultElement> {
  if (result.length === 1){
    return of(results[0]);
  }
    const dialogConfig = new MatDialogConfig<ResultElement[]>();
    dialogConfig.data = results;

    const dialogRef = this.dialog.open(ResultDialogComponent, dialogConfig);

    return dialogRef.afterClosed();
}

我会用下面的方法解决:

  1. 通过订阅获取数据(不使用管道)。并将此数据保存在组件变量中
options: any;

this.apiService
  .apiMethod(input)
  .subscribe(
    (result) => {
       if (result.length === 1) {
         this.router.navigate([result[0]]);
         return;
       }
       options = result;
  }
)
  1. 在模式上使用 ngIf(条件是选项数组的长度 > 0 在接收到数据时显示具有不同选择的组件
<modal-component *ngIf="options.length > 0"></modal-component>
  1. 当用户(单击)您的模式内的选项时,使用路由器进行重定向。
html
<div (click)="redirect(value)">option 1</div>

ts
redirect(value) {
    this.router.navigate([value]);
}

那将是最直接的

两个可能的选择:

  1. 通过用户输入或使用一个元素获得 api 结果的副作用,为所选结果设置一个“下一个”主题。
  2. 跟踪组件的总体状态并在状态中设置 selectedResult 时做出适当响应。

下面的示例是使用 Observable 跟踪组件状态的草图。

  • 有两个输入流进入状态,来自api的结果和用于选择结果的用户输入。
  • 每个流都被转换成一个会修改整体状态的 reducer 函数。
  • UI 应该通过异步管道订阅此状态,在适当的时候显示模态,并通过 Subjects 从事件更新更新状态。
  • 当 selectedResult 有值时,重定向应该影响状态的改变。
readonly getResultsSubject = new Subject<MyInput>();
readonly resultSelectedSubject = new Subject<ResultType>();

private readonly apiResults$ = this.getResultsSubjects.pipe(
  switchMap((input) => this.apiMethod(input))
);

readonly state = combineLatest([
  this.apiResults$.pipe(map(results => (s) => results.length === 1 
    ? { ...s, results, selectedResult: x[0], showModal: false }
    : { ...s, results, showModal: results.length > 1 })),
  this.resultSelectedSubject.pipe(map(selectedResult => (s) => ({ ...s, selectedResult })))
]).pipe(
  scan((s, reducer) => reducer(s), { }),
  shareReplay(1)
);

ngOnInit() {
  this.state.pipe(
    filter(x => !!x.selectedResult)
  ).subscribe(x => this.router.navigate('elements/', x.selectedResult.id));
}

我最近经常使用这种模式。这使得状态的动作和属性的数量很容易增长。

我会创建一个 DialogComponent,它将选项列表作为输入,并在它关闭时发出所选项目。

然后,创建一个辅助方法(可以称之为 promptUser),它只是 return 一个发出选定值的可观察对象:

this.apiService.apiMethod(input)
    .pipe(
        switchMap(results => results.length > 1
            ? this.promptUser(results)
            : of(results[0])
        )
    )
    .subscribe(
        result => this.router.navigate('elements/', result.id)
    );

这里我们简单地使用 switchMap 到 return 一个发出正确项目的 Observable。如果长度大于 1,我们 return 显示对话框并发出所选项目的辅助方法,否则只发出第一个 (only) 项目。请注意,我们用 of 包装了普通值,因为在 switchMap 中,我们需要 return 可观察。

无论哪种情况,您的订阅回调都会发出和接收所需的项目。