简体   繁体   English

来自多个HTTP请求的RxJs / Angular加载以及每个请求的更新模型

[英]RxJs/Angular loading from multiple HTTP requests and updating model from each request

I have been using Angular/RxJS for a few weeks and have various models that are built from multiple REST requests which I have thus far achieved using switchMap(). 我已经使用Angular / RxJS几周了,并且有各种各样的模型,这些模型都是根据我使用switchMap()实现的多个REST请求构建的。 Here is a simple contrived example (stackblitz: https://stackblitz.com/edit/angular-o1djbb ): 这是一个简单的人为例子(stackblitz: https ://packblitz.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));
  }
}

And a simple template: 还有一个简单的模板:

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

This works but the order info is not rendered until both requests complete. 这有效,但在两个请求完成之前不会呈现订单信息。 What I would like to happen is for the itemName to render as soon as it's available and then details to render when they become available. 我想要发生的是itemName一旦可用就呈现,然后在它们可用时呈现细节。 Hence taking advantage of the multiple values that Observables can emit. 因此利用了Observables可以发出的多个值。 Eg: 例如:

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

I realise I could probably achieve this with a BehaviourSubject or using Redux (as I have in the past with React), but feel there is a simple solution I can't quite grasp due to the newness of all this for me. 我意识到我可以使用BehaviourSubject或使用Redux(就像我过去使用React一样)实现这一点,但感觉有一个简单的解决方案,由于所有这些对我来说都是新的,我无法理解。

Use expand to emit the data from the first request straight away and subsequently do the second request. 使用expand直接从第一个请求发出数据,然后执行第二个请求。

expand will call the inner request recursively, getting the order from the last request as input, so we only execute the second request when the order has no details and end the recursion otherwise with an EMPTY Observable. 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 https://stackblitz.com/edit/angular-umqyem

The returned Observable will emit: 返回的Observable将发出:

  1. { "id": 123, "itemName": "foo", "details": null }
  2. { "id": 123, "itemName": "foo", "details": "Some details about foo" }

Maybe your best choice is to substitute mergeMap() for switchMap() ? 也许你最好的选择是用mergeMap()代替switchMap()

https://blog.angular-university.io/rxjs-higher-order-mapping/ https://blog.angular-university.io/rxjs-higher-order-mapping/

As we can see, the values of the merged source Observables show up immediately in the output. 正如我们所看到的,合并的源Observables的值立即显示在输出中。 The result Observable will not be completed until all the merged Observables are completed. 在完成所有合并的Observable之前,结果Observable将不会完成。

I would definitely check out these articles: 我肯定会查看这些文章:

Instead of persisting your full item at the end of the observable stream (ie in your .subscribe block) you could store the received values within the respective parts of your code, namely after they are available: 您可以将接收到的值存储在代码的相应部分中,即在它们可用之后,而不是在可观察流的末尾(即在.subscribe块中) .subscribe完整项目:

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

... and then in your sibscription block you would only have to store the advanced information: ...然后在您的sibscription块中,您只需存储高级信息:

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

Obviously, this requires you to initialize your ´order´ attribute accordingly, eg with order: Order = {}; 显然,这需要您相应地初始化您的'order'属性,例如使用order: Order = {}; or order: Order = new Order(); 或者order: Order = new Order(); .

Basically, the idea is to restructure your code st your observable stream emits partial values (your order's attributes) instead of a full-blown object. 基本上,我们的想法是重构您的代码,您的可观察流会发出部分值(您的订单属性)而不是完整的对象。

One solution I did come up with was to just create an Observable and call next(). 我提出的一个解决方案是创建一个Observable并调用next()。 I'd be interested to hear any opinion on this. 我有兴趣听到有关此事的任何意见。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM