Angular 基于响应递归组合HTTP结果

Angular 基于响应递归组合HTTP结果,angular,recursion,rxjs,angular-httpclient,Angular,Recursion,Rxjs,Angular Httpclient,有一个API(),我想将所有数据用于我的angular应用程序。问题是他们的API有分页功能,我想一次检索所有内容 正如您在API上看到的,它们的响应中实际上有“next”属性,该属性指向下一页。我希望只要“next”属性不为null,就能够继续从API请求,然后将它们的所有响应合并为一个 我尝试过使用递归,但当它到达第二个循环时,我得到了未定义的值。我猜这是因为异步请求,所以我没有定义 下面是我的代码 @Injectable() export class GenomicsEnglandServ

有一个API(),我想将所有数据用于我的angular应用程序。问题是他们的API有分页功能,我想一次检索所有内容

正如您在API上看到的,它们的响应中实际上有“next”属性,该属性指向下一页。我希望只要“next”属性不为null,就能够继续从API请求,然后将它们的所有响应合并为一个

我尝试过使用递归,但当它到达第二个循环时,我得到了未定义的值。我猜这是因为异步请求,所以我没有定义

下面是我的代码

@Injectable()
export class GenomicsEnglandService {
    panels = [];
    constructor(private http: HttpClient) {
    }

    getPanels(url): Observable<any>{
        const headers = new HttpHeaders()
        .append('Content-Type', 'application/json')
        .append('Accept', '*/*');

        return this.http.get(url, {headers: headers})
            .map((data) => {
                panels = panels.concat(data.results);
                if(data.next){
                    this.getPanels(data.next);
                }else{
                    return panels;
                }
            })
            .catch((e) => {
                Raven.captureMessage("GENOMICS ENGLAND ERROR: " + JSON.stringify(e));
                return of([]);
            });

    }

}

我在我的一个项目中做过类似的实现

在我的服务中(我回报你的承诺,如果你愿意,你可以回报observable)

在我的组件中

this.service.getData(url).then((response) => {
  console.log(response);
}).catch(err => console.log(err))

如果我理解了您的意图,那么您希望使用mergeMap而不是map。“合并贴图”(Merge map)允许您按顺序组合观测值,如下所示:

getPanels(url):可观察{
返回此.http.get(url)
.烟斗(
合并映射(数据=>{
panels=panels.concat(数据结果);
if(data.next){
返回this.getPanels(data.next);
}否则{
返回面板;
}
})
);
}

这行吗?

更新了请参阅@user2216584的答案。这个答案被接受了,但最好是给出答案

构造函数(私有httpClient:httpClient){}
恩戈尼尼特()
{
这个.getHttp('https://panelapp.genomicsengland.co.uk/api/v1/panels/,空)。订阅(res=>{
this.data=res
})
}
getHttp(url,fullData:any[]):可观察
{
fullData=fullData | |[]
返回此.httpClient.get(url).pipe(
switchMap((数据:任意)=>{
fullData=fullData.concat(data.results);
return!data.next?of(fullData):
this.getHttp(data.next,fullData)
})
)
}

这可以在rxjs中通过使用
expand
操作符轻松完成:

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

export interface PanelResponse {
  results: object[];
  next: string|null;
}

@Injectable()
export class Service {
  private readonly baseUrl = 'https://panelapp.genomicsengland.co.uk/api/v1/panels/';

  constructor(private http: HttpClient) {
  }

  getPanels(): Observable<object[]>{
    return this.get(this.baseUrl).pipe(
      expand(({next}) => next ? get(next) : empty()),
      map(({results}) => results),
      // if you want the observable to emit 1 value everytime that
      // a page is fetched, use `scan` instead of `reduce`
      reduce((acc, val) => acc.concat(val), new Array<object>()),
    );
  }

  private get(url:string>):Observable<PanelResponse> => this.http.get<PanelResponse>(url);
}
从'rxjs'导入{empty,Observable};
从“rxjs/operators”导入{expand,map,reduce};
导出接口面板响应{
结果:对象[];
下一步:字符串| null;
}
@可注射()
出口类服务{
专用只读baseUrl=https://panelapp.genomicsengland.co.uk/api/v1/panels/';
构造函数(专用http:HttpClient){
}
getPanels():可观察{
返回this.get(this.baseUrl).pipe(
展开({next})=>next?get(next):empty(),
映射(({results})=>results),
//如果您希望每次观察时,可观察对象都发出1个值
//获取页面时,请使用'scan'而不是'reduce'`
减少((acc,val)=>acc.concat(val),新数组(),
);
}
私有get(url:string>):Observable=>this.http.get(url);
}

虽然这个问题已经得到了回答,但我想提出另一种方法,即使用
展开
运算符[。
展开
运算符是为了这种递归目的而产生的:

getResult() {

    const url = "https://panelapp.genomicsengland.co.uk/api/v1/panels/";

    return this.getResponse(url)
                    .pipe(
                      expand((res: any) => this.getResponse(res.next)),
                      takeWhile((res: any) => res.next, true),
                      concatMap((res: any) => res.results),
                      reduce((acc, val) => {
                        acc.push(val);
                        return acc;
                      }, []),
                      tap(_ => {
                        console.log(_);
                        this.loading = false;
                      })
                    )

  }

  getResponse(url) {
    return this.httpClient.get(url);
  }

请参阅“工作”

“this.getPanels(data.next);“爱你的答案,我已将if-else条件更改为takeWhile,并将
inclusive last
参数设置为true。@benshabatnoam哦,是的!我们可以使用
takeWhile
而不是if/else。谢谢您的编辑。@user2216584,太好了!
constructor(private httpClient:HttpClient){}
  ngOnInit()
  {

    this.getHttp('https://panelapp.genomicsengland.co.uk/api/v1/panels/',null).subscribe(res=>{
      this.data=res
    })

  }

  getHttp(url,fullData:any[]):Observable<any[]>
  {
    fullData=fullData || []
    return this.httpClient.get(url).pipe(
      switchMap((data:any)=>{
        fullData=fullData.concat(data.results);
        return !data.next? of(fullData):
               this.getHttp(data.next,fullData)
      })
    )
  }
import {empty, Observable} from 'rxjs';
import {expand, map, reduce} from 'rxjs/operators';

export interface PanelResponse {
  results: object[];
  next: string|null;
}

@Injectable()
export class Service {
  private readonly baseUrl = 'https://panelapp.genomicsengland.co.uk/api/v1/panels/';

  constructor(private http: HttpClient) {
  }

  getPanels(): Observable<object[]>{
    return this.get(this.baseUrl).pipe(
      expand(({next}) => next ? get(next) : empty()),
      map(({results}) => results),
      // if you want the observable to emit 1 value everytime that
      // a page is fetched, use `scan` instead of `reduce`
      reduce((acc, val) => acc.concat(val), new Array<object>()),
    );
  }

  private get(url:string>):Observable<PanelResponse> => this.http.get<PanelResponse>(url);
}
getResult() {

    const url = "https://panelapp.genomicsengland.co.uk/api/v1/panels/";

    return this.getResponse(url)
                    .pipe(
                      expand((res: any) => this.getResponse(res.next)),
                      takeWhile((res: any) => res.next, true),
                      concatMap((res: any) => res.results),
                      reduce((acc, val) => {
                        acc.push(val);
                        return acc;
                      }, []),
                      tap(_ => {
                        console.log(_);
                        this.loading = false;
                      })
                    )

  }

  getResponse(url) {
    return this.httpClient.get(url);
  }