首页 > 解决方案 > 快速重复 HTTP 调用的 Angular/RXJS 数据问题

问题描述

我正在开发一个企业应用程序,该应用程序被另一个角度应用程序用作角度元素。

我们的应用程序从父应用程序接收一些输入,这些输入启动了一堆 api 调用,然后显示数据。我们开始遇到的一个问题是当我们从父应用程序获得快速、重复的输入时。

例如,父应用程序可能输入 {"name": "Fred", "id": "abc"},然后我们的应用程序开始使用我们在组件中订阅的 Angular 服务启动一堆 API 调用。然而,如果父组件在 {"name": "Fred", "id": "abc"} 输入,在我们的一些 API 调用完成之前。在这些情况下,我们有时会显示属于错误人员的数据。

如果有帮助或发布一些修改过的代码,我可以详细说明,不要认为我可以分享我们的实际代码。我希望得到一些关于如何处理这种情况的一般性建议或技巧。

我开始尝试做的一件事是,每次我们收到输入时检查订阅是否已经存在,重置我们所有的显示数据并取消订阅服务,然后再次开始订阅。我目前不确定这是否解决了问题。

我希望得到一些建议的另一件事是如何测试这种情况。这是可以使用 jasmine/karma 测试的东西(并且以某种方式故意操纵模拟服务中的响应时间),还是更适合 Protractor/Cypress 测试的东西?

谢谢!

编辑——这是一个不完美的 stackblitz 示例,其中 home 组件将成为焦点,它接受来自应用程序组件的输入

https://stackblitz.com/edit/angular-q4bv9y?file=src%2Fapp%2Fapp.module.ts

标签: angularrxjs

解决方案


这是我的方法:

export class HomeComponent {
    private foodSource = new Subject<string>();

    hasNoFavoriteFood = false
    isLoading = false;
    foodString$: Observable<string>;

    @Input() 
    set customer(customer: Customer) {
    this._customer = customer;
    this.foodSource.next(
            customer.hasFavoriteFood && customer.ID || null
        );
  }

    ngOnInit () {
        this.foodString$ = this.foodSource.pipe(
            // You might also want to add `debounce(ms)`
            // if the `setter` is called very often in a small amount of time
            // debounce(300ms) // once 300ms passed without the parent sending anything, proceed to making the req

            // Assumed that if `hasFavoriteFood === false`, a falsy value will be sent
            tap(id => this.hasNoFavoriteFood = !id),

            // Only the truthy values
            filter(v => !!v)
            switchMap(
                id => (
                    this.isLoading = true,
                    this.customerAPI.getCustomerFoodPreferences(id).pipe(
                        // Adding your retry logic
                        retryWhen(errors$ => errors$.pipe(/* ... */)),
                        finalize(() => this.isLoading = false)
                    )
                )
            ),

            // Might want to start with a default/falsy value
            startWith(null),
        )
    }
}

switchMap几乎解决了你的问题。假设父级发送AswitchMap将创建一个内部可观察对象getCustomerFoodPreferences。但是,当B发送时,如果switchMap已经有一个活动的内部 observable,它将被取消订阅并创建一个新的,基于B.

finalize()每当取消订阅源(在这种情况下是由 产生的可观察对象getCustomerFoodPreferences)时都会调用。也就是说,当源发出错误/完成通知时,或者当源显式取消订阅时,这就是switchMap新值到达时所做的事情。

你的模板会像这样使用它:

{{ foodString$ | async }}

推荐阅读