【问题标题】:How to use Observables for pagination at REST-API endpoint?如何在 REST-API 端点使用 Observables 进行分页?
【发布时间】:2019-01-12 14:10:18
【问题描述】:

我正在使用 Angular 7 并构建一个从 REST-API 端点检索数据的 web 应用程序。用户将他们的搜索字符串放入一个常见的formControl 字段中,该字段会被观察到。如果输入字段中的值发生变化,则会向 API 端点发送一个 http 请求。最后,我将 http 响应作为我可以订阅的 observable 获得。但是查询结果也可以有100多个项目。然后 API 端点将 NEXT 链接发送到第二页,依此类推(API 端点分页)。

我现在的问题是,我找不到正确的方法来观察搜索输入字段并遍历从 API 端点获得的所有 NEXT 页面。单独使用它就像一个魅力。

有人对我的用例有好的做法吗?

我的组件文件:

export class GeomapComponent implements OnInit {
  searchTerm = new FormControl();
  constructor(private geomapService: GeomapService) { }

  ngOnInit() {
    this.searchTerm.valueChanges.pipe(
      debounceTime(400),
      distinctUntilChanged(),
      switchMap(term => this.geomapService.retrieveApi(term))
    ).subscribe(data => {
      if (data['results']) {
       ......
      }
    }
  }
}

我的服务文件:

export class GeomapService {
  retrieveUrl = new Subject<string>();

  constructor(private http: HttpClient) { }

  public retrieveApi(searchTerm: string): Observable<ApiDataResponse> {
    this.retrieveUrl.next(baseApiUrl + '?q=' + searchTerm);
    return this.retrieveUrl.pipe(
      switchMap(url => this.sendRequest(url)),
    ).subscribe(data => {
      if (data['next']) {
        this.retrieveUrl.next(data['next']);
      }
    });
  }

  public sendRequest(url: string, httpOptions = {}): Observable<ApiDataResponse> {
    return this.http.get<ApiDataResponse>(url);
  }

}

不幸的是,我收到以下错误:

TS2322:“订阅”类型不可分配给“可观察”类型。 “订阅”类型中缺少属性“_isScalar”。

更新: 现在我走得更远了,因为我意识到我必须在服务中合并/合并顺序传入的 observables(由循环提供)。

  public requestApi(requestUrl: string): Observable<ApiDataResponse> {
    return this.sendRequest(requestUrl).pipe(
      mergeMap(result => {
        if (result['next']) {
          return this.requestApi(result['next']);
        } else {
          return of(result);
        }
      })
    );
  }

尽管如此,我仍然坚持使用正确的组合/转换运算符,因为在这个阶段,mergeMap 我订阅了只是最后一个响应的结果。但我也希望获得之前收到的所有回复并将其合并为一个值。

有人知道运算符中的返回应该是什么样子吗?

更新: 代码更新,移除数组问题 (ApiDataResponse[] --> ApiDataResponse)

【问题讨论】:

  • 在你的代码中data 的类型是ApiDataResponse[],对吧?!你怎么会有data['next']data['result'] 属性在一个数组上?
  • @fridoo: data['next']data['result'] 是 API 响应的值。
  • 是的,我是这么认为的。但是你总是返回/switchMap/mergeMap 到anObservable&lt;ApiDataResponse[]&gt;。所以你的dataresult 变量将是ApiDataResponse[] 类型。
  • @fridoo:您对提到的数组问题是正确的。我修好了它。谢谢。然而,这不是我的问题的解决方案(通知其他人)。
  • 能否更新您问题中的代码。

标签: angular rxjs httpclient


【解决方案1】:

所以,如果我理解得很好,你在搜索字段上有一个 observable,当你搜索时,你调用 API 来获取结果。如果结果 > 100,那么您的 API 会发送前 100 个结果并告诉您对接下来的 100 个结果进行另一个请求,直到没有更多结果为止。

对我来说,您需要一次获得所有结果有点奇怪,不是发送 100 个第一个结果来等待用户请求下一个 100 个结果(例如,通过单击“下一页按钮”或滚动到某个限制)?

我会通过将最初的前 100 个结果存储在显示它们的组件中(可能在 BehaviourSubject 中)以及何时获取结果的下一部分(例如,当滚动位置达到一定数量时)来解决这个问题或者当用户点击下一个按钮时),如果有任何结果,我会请求下一个结果......

【讨论】:

  • 感谢您的想法。我也有同样的想法,但我需要一次将所有数据可视化为一个整体。
【解决方案2】:

我同意@LaetitiaDallinge 关于一次请求所有数据是否真的有必要或适当的观点。您通常应该尽量减少您发出的 http 请求的数量,并且只在真正需要时才获取数据。

如果必须一次获取所有数据,可以使用expand

import { EMPTY } from 'rxjs';
import { expand, reduce, map } from 'rxjs/operators';

// I presumed that you get an object of this type back from your API.
interface ApiDataResponse {
  next: string,
  results: Things[]
}

public fetchAllData(searchTerm: string): Observable<Things[]> {
  return this.http.get<ApiDataResponse>(baseApiUrl + '?q=' + searchTerm)
    .pipe( 
      // we recursively call the GET requests until there is no 'next' url
      expand(apiResponse => apiResponse.next
        ? this.http.get<ApiDataResponse>(apiResponse.next)
        : EMPTY;
      }),
      // we map the api response to the data we actually want to return
      map(apiResponse => apiResponse.results),
      // we reduce the data of all GET requests to a single array
      reduce((accData, data) => accData.concat(data), [])
    )
}

【讨论】:

  • 感谢您的回复。你是绝对正确的,数组的问题只是我这边的一个错误,它返回一个空数组的 catchError 运算符。我已经修好了。关于您的代码,empty() return 语句出现错误(TS2349:无法调用类型缺少调用签名的表达式。类型'Observer'没有兼容的调用签名。)
  • 经过一番研究,我发现在 RxJS 6 中我必须使用 EMPTY 而不是 empty()(来源:stackoverflow.com/questions/38548407/…)这样你的代码运行良好。谢谢!最后,我会针对我的用例对其进行修改,并尽快发布对我的代码的更新。
  • @Mike42 好的,我刚刚读到 empty() 在 RxJS 6 中已被弃用,取而代之的是常量 EMPTY。我更新了我的代码,尽管 empty() 在 RxJS 6 中仍然有效。我也向管道添加了map 函数并相应地修改了我的reduce 函数,因为我发现这是必要的。很高兴我能帮助你。如果我的回答解决了您的问题,请接受和/或投票赞成这个答案。谢谢。
【解决方案3】:

您无法退回订阅。使用 switchMap 返回 observable 或 subsciebe

【讨论】:

  • 是的,我注意到了。但是在这种情况下,使用两个可观察对象有什么解决方案?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-12
  • 2017-09-11
  • 2015-05-18
  • 1970-01-01
  • 1970-01-01
  • 2018-11-24
相关资源
最近更新 更多