【问题标题】:How to share HttpClient GET with BehaviorSubject?如何与 BehaviorSubject 共享 HttpClient GET?
【发布时间】:2021-05-12 15:25:42
【问题描述】:

所以,我使用 shareReplay(1) 来缓存从 HttpClient GET 调用返回的内存中的项目数组。

if (!this.getItems$) {
    this.getItems$ = this.httpClient
            .get<Item[]>(url, options)
            .pipe(shareReplay(1));
}
return this.getItems$;

我这样做是因为我在页面上有许多需要项数组的组件,我不想为每个组件都进行 http 调用。

我也在页面上创建一个项目。因此,在我调用服务以调用在数据库中创建项目并返回它的 API 之后,我想将其添加到内存中的项目数组中并提醒所有订阅更新数组的组件.所以,我尝试了这个:

private items = new BehaviorSubject<Item[]>(null);

...

if (!this.getItems$) {
this.getItems$ = this.httpClient
    .get<Item[]>(url, options)
    .pipe(
      tap({
        next: (items) => {
          this.items.next(items);
        },
      })
    )
    .pipe(multicast(() => this.items));
}
return this.getItems$;

然后在调用API的方法中:

return this.httpClient.post<Item>(url, item, options).pipe(
      tap({
        next: (item) => {
          this.items.next(this.items.value.push(item));
        },
      })
    );

问题是,任何订阅 getItems 方法的东西总是返回 null。数据库中有项目,所以即使在第一次调用时,也应该有返回的项目。有,因为我用 shareReplay(1) 对其进行了测试,它可以工作。

如何使用 BehaviorSubject 而不是 ReplaySubject 进行共享?

【问题讨论】:

  • "任何订阅 getItems 方法的东西,总是返回 null" - 订阅者最初是否收到null,然后是来自httpClient.get() 的结果,或者只收到@ 987654327@?
  • 只有空值。项目数组永远不会有项目。
  • 从外观上看,API 没有被多播()调用。查看文档,我需要以某种方式调用 connect() 以将 BehaviorSubject 订阅到源,即 get,这将导致 API 被调用。

标签: angular rxjs angular-httpclient behaviorsubject rxjs-pipeable-operators


【解决方案1】:

对于此代码:

if (!this.getItems$) {                 
    this.getItems$ = this.httpClient 
            .get<Item[]>(url, options)
            .pipe(shareReplay(1));
}
return this.getItems$;

这:this.httpClient.get() 是异步的。所以如果getItems$还没有设置,return语句也不会设置它。

代码按如下顺序执行:

  • 1 号线。
  • 2 号线和 3 号线
  • 第 5 行。(返回 null)
  • 第 4 行。(实际处理返回的响应)

此外,添加 ifshareReplay 是多余的,只有在 Observable 尚未设置时才会自动获取数据。

试试这样的:

getItems$ = this.httpClient
            .get<Item[]>(url, options)
            .pipe(shareReplay(1));

编辑:上面的代码是一个属性,而不是一个方法。

对于关于创建新项目的问题的第二部分,您可以执行以下操作,这是来自我的一个示例。您可以根据场景的需要重命名属性/接口。

  // Action Stream
  private productInsertedSubject = new Subject<Product>();
  productInsertedAction$ = this.productInsertedSubject.asObservable();

  // Merge the streams
  productsWithAdd$ = merge(
    this.productsWithCategory$,  // would be your getItems$
    this.productInsertedAction$
  )
    .pipe(
      scan((acc: Product[], value: Product) => [...acc, value]),
      catchError(err => {
        console.error(err);
        return throwError(err);
      })
    );

您可以在此处找到包含上述代码的完整示例:https://github.com/DeborahK/Angular-RxJS/tree/master/APM-Final

编辑:

作为参考,这里是来自 stackblitz 的关键代码:

服务

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { merge, Observable, Subject, throwError } from 'rxjs';
import { catchError, scan, shareReplay, tap } from 'rxjs/operators';
import { Item } from './item.model';

@Injectable({
  providedIn: 'root'
})
export class ItemsService {
  url = 'https://jsonplaceholder.typicode.com/users';

  // This is a property
  // It executes the http request when subscribed to the first time
  // And emits the returned array of items.
  // All other times, it replays (and emits) the items due to the shareReplay.
  getItems$ = this.httpClient.get<Item[]>(this.url).pipe(
    tap(x => console.log("I'm only getting the data once", JSON.stringify(x))),
    tap(x => {
      // You can write any other code here, inside the tap
      // to perform any other operations
    }),
    shareReplay(1)
  );
  
    // Action Stream
  private itemInsertedSubject = new Subject<Item>();
  productInsertedAction$ = this.itemInsertedSubject.asObservable();

  // Merge the action stream that emits every time an item is added
  // with the data stream
  allItems$ = merge(
    this.getItems$,
    this.productInsertedAction$
  )
    .pipe(
      scan((acc: Item[], value: Item) => [...acc, value]),
      catchError(err => {
        console.error(err);
        return throwError(err);
      })
    );

  constructor(private httpClient: HttpClient) {}

  addItem(item) {
    this.itemInsertedSubject.next(item);
  }
}

组件

import { Component, OnInit } from '@angular/core';
import { Item } from './item.model';
import { ItemsService } from './items.service';

@Component({
  selector: 'app-item',
  templateUrl: './item.component.html'
})
export class ItemComponent {

  // Recommended technique using the async pipe in the template
  // With this technique, no ngOnInit is required.
  items$ = this.itemService.allItems$;

  constructor(private itemService: ItemsService) {}

  addItem() {
    this.itemService.addItem({id: 42, name: 'Deborah Kurata'})
  }
}

模板

<h1>My Component</h1>
<button (click)="addItem()">Add Item</button>
<div *ngFor="let item of items$ | async">
  <div>{{item.name}}</div>
</div>

【讨论】:

  • 我尝试不使用 if 并且它为每个订阅者拨打电话。 this.getItems$ = this.httpClient.get&lt;Item[]&gt;(url, options).pipe(shareReplay(1)); return this.getItems$;
  • 你能否创建一个 small stackblitz 来展示你的技术和你的问题(而不是你的整个应用程序)。它不应该为每个订户打电话。请注意return 语句将返回结果,因为.get 是异步的。
  • 是的,我要返回 Observable。在第一个订阅者订阅之前,get 不应触发。我会尝试做一个 Stackblitz 项目。
  • 太好了,谢谢!作为一个属性,在订阅该属性之前它不会触发。我在上面引用的 GitHub 存储库中有一个完整的示例。 (如果您对链接感兴趣,我有几个 YouTube 视频和关于这些主题的 Pluralsight 课程。)
  • 为了确定您希望从 sharereplay 中获得哪种行为,我将使用较新的语法 sharereplay( { refcount : false , buffercount : 1 } ) refcount = false 即使没有订阅,也会保留结果。取决于你想要的行为方式
【解决方案2】:

为了回答我的问题,我找到了解决方案。

首先,我必须获得最新版本的 rxjs:

npm install rxjs@latest

然后,我创建了一个 shareBehavior 类:

export function shareBehavior<T>(
  behaviorSubject: BehaviorSubject<T>
): MonoTypeOperatorFunction<T> {
  return share<T>({
    connector: () => behaviorSubject,
    resetOnError: true,
    resetOnComplete: false,
    resetOnRefCountZero: false,
  });
}

那么,在服务中:

private items = new BehaviorSubject<Item[]>([]);

...

if (!this.getItems$) {
  this.getItems$ = this.httpClient
  .get<Item[]>(url, options)
  .pipe(shareBehavior(this.items));
}
return this.getItems$;

这很好用。但是,这样做的问题是,因为行为具有默认值,所以所有订阅者都会收到 [] 的初始值。一旦 API 调用完成,它们就会更新。但是,我宁愿使用 ReplaySubject 以便订阅者仅在 http get 返回时获得一个值。这样做,请参阅:https://stackoverflow.com/a/67508863/787958

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-01-29
    • 2021-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多