简体   繁体   English

Reactive Loading state 管理用 RxJs

[英]Reactive Loading state management with RxJs

A classic task when you have some input field and you have to fetch something on values changes.当你有一些输入字段并且你必须在值更改时获取一些东西时的经典任务。 Let's imagine we use Angular Reactive Forms. Example:假设我们使用 Angular Reactive Forms。示例:

orders$ = inputControl.valueChanges.pipe(
   switchMap((value) => {
     return someService.fetch(value);
   })
);

Now we should also somehow manage loading state. I usually use tap :现在我们还应该以某种方式管理加载 state。我通常使用tap

orders$ = inputControl.valueChanges.pipe(
  tap(() => { loading = true }), // or loading$.next(true) if loading is a subject
  switchMap((value) => {
    return someService.fetch(value);
  }),
  tap(() => { loading = false }), // or loading$.next(false) if loading is a subject
);

However it seems we can somehow avoid assigning values in the tap and use RxJs instead.然而,我们似乎可以通过某种方式避免在tap中赋值,而是使用 RxJs 代替。 But I could not find a way to do with it.但是我找不到处理它的方法。

For me, the usage of the ideal solution would be对我来说,理想解决方案的用法是

orders$ = <some abstraction here that depends on inputControl.valueChanges and fetching>
loading$ = <some abstraction here that depends on fetching>

You can use the map and shareReplay operators to achieve this.您可以使用 map 和 shareReplay 运算符来实现此目的。 The map operator can be used to return the value from the service call and the shareReplay operator can be used to share the observable and keep the loading state. map 运算符可用于从服务调用返回值,shareReplay 运算符可用于共享可观察对象并保持加载 state。

 orders$ = inputControl.valueChanges.pipe( switchMap((value) => { return someService.fetch(value).pipe( map(data => { loading$.next(false); return data; }), startWith({loading: true}) ); }), shareReplay(1) ); loading$ = orders$.pipe(map(data => data.loading));

This way you can use the orders$ observable to subscribe to the orders and loading$ observable to subscribe to the loading state. Note: The above example will not work if you return a object from service call, you would need to wrap the object in another object with loading property like {data: {}, loading: true/false}这样您就可以使用 orders$ observable 来订阅订单,并使用 loading$ observable 来订阅加载 state。注意:如果您从服务调用返回 object,上面的示例将不起作用,您需要将 object 包装在另一个 object 具有加载属性,如 {data: {}, loading: true/false}

Take a look at my library ez-state.看看我的图书馆 ez-state。

https://github.com/adriandavidbrand/ngx-ez/tree/master/projects/ez-state https://github.com/adriandavidbrand/ngx-ez/tree/master/projects/ez-state

https://adrianbrand.medium.com/angular-state-management-using-services-built-with-ez-state-9b23f16fb5ae https://adrianbrand.medium.com/angular-state-management-using-services-built-with-ez-state-9b23f16fb5ae

orderCache = new EzCache<Order[]>();

order$ = this.orderCache.value$;

loading$ = this.orderCache.loading$;

inputControl.valueChanges.subscribe(value => {
  orderCache.load(someService.fetch(value));
});

An EzCache is a glorified behaviour subject that has methods load, save, update and delete and manages state observables like loading$, loaded$, saving$, saved$ etc. EzCache 是一个美化的行为主题,它具有加载、保存、更新和删除方法,并管理 state 个可观察对象,如加载 $、加载 $、保存 $、保存 $ 等。

I would personally would have the cache in the service and expose the observables from the service like in that article I wrote.我个人会在服务中拥有缓存,并像我写的那篇文章那样公开服务中的可观察对象。

If you think about the loading state as part of a larger "order search request", then it becomes easier to see how to use RxJS to create an observable that emits the desired overall state.如果您将加载 state 视为更大的“订单搜索请求”的一部分,那么就更容易理解如何使用 RxJS 创建一个发出所需整体 state 的可观察对象。

Instead of having two separate observables, orders$ and loading$ , you could have a single observable that emits both pieces of data ( since they are always changed at the same time ).不用两个单独的 observable, orders$loading$ ,你可以有一个单独的 observable 发出两条数据(因为它们总是同时改变)。

We basically want to create an observable that initially emits:我们基本上想要创建一个最初发出的可观察对象:

{ 
    isLoading: true, 
    results: undefined
}

then, after the results are received, emits:然后,在收到结果后,发出:

{ 
    isLoading: false, 
    results: ** orders from api call ** 
}

We can achieve by using switchMap , map , and startWith :我们可以通过使用switchMapmapstartWith来实现:

  orderSearchRequest$ = this.searchTerm$.pipe(
    switchMap(term => this.searchService.search(term).pipe(
      map(results => ({ isLoading: false, results })),
      startWith({ isLoading: true, results: undefined })
    )),
  );
<form [formGroup]="form">
  <input formControlName="searchTerm" placeholder="Order Search">
</form>

<div *ngIf="orderSearchRequest$ | async as request">

  <div *ngIf="request.isLoading"> loading... </div>

  <ul>
    <li *ngFor="let order of request.results">
      {{ order.description }}
    </li>
  </ul>

</div>

Here's a working StackBlitz demo.这是一个有效的StackBlitz演示。

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

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