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