RxJs/Angular多个HTTP请求的加载和每个请求更新模型

4

我已经使用Angular / RxJS几周了,并且有多个模型是从多个REST请求中构建的,到目前为止我使用switchMap()实现了这一点。这里是一个简单的虚构示例(stackblitz:https://stackblitz.com/edit/angular-o1djbb):

import { Component, OnInit, } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay, switchMap } from 'rxjs/operators';

interface Order {
  id: string;
  itemName: string;
  details?: string;
}

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  order: Order;

  ngOnInit() {
    this.getOrderFromApi(123)
      .subscribe(item => this.order = item);
  }

  getOrderFromApi(id): Observable<Order>  {
    const item$ = this.getItemName(id);
    const fullItem$ = item$.pipe(
      switchMap(n => {
        console.log(`Got name: '${n}''`);
        return this.getDetails(n);
      },
        (nameReq, detailsReq) => ({ id: '123', itemName: nameReq, details: detailsReq })
      ));
    return fullItem$;
  }

  getItemName(id): Observable<string> {
    return this.fakeXhr('foo');
  }

  getDetails(itemName): Observable<string> {
    console.log(`Got details '${itemName}''`)
    return this.fakeXhr('Some details about foo');
  }

  fakeXhr(payload: any) {
    return of(payload)
      .pipe(delay(2000));
  }
}

还有一个简单的模板:

<p>
  item: {{order && order.itemName}}
</p>
<p>
  details: {{order && order.details}}
</p>

这样做可以实现功能,但是订单信息直到 两个 请求都完成后才会被渲染。我希望的是,在可用时尽快呈现 itemName,然后在详情可用时再呈现它们。因此,利用可观察对象可以发出多个值的优势,例如:

// first value emitted:
{ itemName: 'foo', details: null }
// second value emitted:
{ itemName: 'foo', details: 'Some details about foo' }

我意识到我可能可以使用BehaviorSubject或Redux(就像我以前在React中做的那样)来实现这一点,但是由于这对我来说还很新,所以感觉有一个简单的解决方案我无法完全掌握。

4个回答

2
使用expand可以立即发出第一个请求的数据,然后执行第二个请求。 expand将递归调用内部请求,以上一个请求的order作为输入,因此仅当order没有details时才执行第二个请求,并使用EMPTY Observable结束递归。
import { Observable, EMPTY } from 'rxjs';
import { map, expand } from 'rxjs/operators';

getOrderFromApi(id): Observable<Order> {
  return this.getItemName(id).pipe(
    map(itemName => ({ id, itemName, details: null } as Order)),
    expand(order => order.details
      ? EMPTY
      : this.getDetails(order.itemName).pipe(
        map(details => ({ id, itemName: order.itemName, details } as Order))
      )
    )
  );
}

https://stackblitz.com/edit/angular-umqyem

返回的Observable将会发出以下内容:
  1. { "id": 123, "itemName": "foo", "details": null }
  2. { "id": 123, "itemName": "foo", "details": "关于foo的一些细节" }

我将此标记为答案,因为我认为这是最优雅的解决方案(目前至少是如此)。我们可以进一步使用扩展中的 index 来实现类似以下方式:https://stackblitz.com/edit/angular-btso9u?file=src/app/app.component.ts - Jai
谢谢,expand 也非常适合执行未知数量的请求,例如当您处理分页时。https://dev59.com/WLHma4cB1Zd3GeqPI0Rv#54293624 - frido

0

直接替换肯定行不通:https://stackblitz.com/edit/angular-f8canj?file=src/app/app.component.tsFork那个stackblitz并向我们展示如何做到! - Jai

0

不要在可观察流的末尾(即在.subscribe块中)持久化完整项,而是应该在相应的代码部分内存储接收到的值,即在它们可用之后:

 switchMap(n => {
        console.log(`Got name: '${n}''`);
        this.order.name = n;
        return this.getDetails(n);
      },

然后在您的订阅模块中,您只需要存储高级信息:

this.getOrderFromApi(123)
      .subscribe(item => this.order.details = item.details);

显然,这需要您相应地初始化´order´属性,例如使用order: Order = {};order: Order = new Order();
基本上,思路是重新构造您的代码,使你的可观察流以部分值(您订单的属性)而不是完整对象的形式发出。

这在这个人为的例子中可能有效,但实际上 getOrderFromApi() 将会在一个服务中,那么实现上述目标的唯一方法就是将一个对象的引用传递给它 getOrderFromApi(this.order) ,这并不是我想要的。我希望从返回的 Observable 中发出多个值。 - Jai

0

我想到的一个解决方案是创建一个Observable并调用next()。我很想听听您的意见。

getOrder(id) {
    const ob$ = new Observable(subscriber => {
      const item$ = this.getItem(id)
        .pipe(
          tap(o => subscriber.next(({ id: id, itemName: o.itemName, details: null }))),
          switchMap(o => this.getDetails(o.itemName),
            (o, d) => ({ id: id, itemName: o.itemName, details: d.details })
          ),
          tap(a => subscriber.next(a))
        );
      item$.subscribe(a => {
        subscriber.complete();
      });
    });

    return ob$;
  }

Stackblitz: https://stackblitz.com/edit/angular-mybvli

Stackblitz: https://stackblitz.com/edit/angular-mybvli


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接