有一个API(https://panelapp.genomicsengland.co.uk/api/v1/panels/?page=1),我想将所有数据消耗到我的角度应用程序中。问题在于他们的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([]);
});
}
}
然后从我刚刚调用的组件
this.GenomicsEnglandService.getPanels('https://panelapp.genomicsengland.co.uk/api/v1/panels/?page=1').subscribe(data => {
console.log(data);
})
答案 0 :(得分:3)
尽管已经回答了这个问题,但我想提出另一种方法,使用expand
运算符[https://rxjs-dev.firebaseapp.com/api/operators/expand]。 expand
运算符用于此类递归目的:
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);
}
查看有效的stackblitz
答案 1 :(得分:1)
已更新,请参见@ user2216584的答案。这个答案是可以接受的,但最好是指示答案
请参阅stackblitz
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)
})
)
}
答案 2 :(得分:1)
这可以通过使用expand
运算符在rxjs中轻松完成:
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);
}
答案 3 :(得分:0)
如果我了解您要执行的操作,则希望使用mergeMap而不是map。合并映射使您可以按顺序合并可观察对象,如下所示:
getPanels(url): Observable<any> {
return this.http.get(url)
.pipe(
mergeMap(data => {
panels = panels.concat(data.results);
if(data.next) {
return this.getPanels(data.next);
} else {
return panels;
}
})
);
}
行得通吗?
答案 4 :(得分:-1)
我在一个项目中也做了类似的实现。
在我的服务中(我答应您可以返回观察到的内容)
getDataHttp(url) {
return this.http.get(url).toPromise();
}
getData(url) {
let response = {};
let data = [];
return new Promise(async (resolve, reject) => {
try {
do {
response = await this.getDataHttp(url);
data.push(response);
} while(response['next']);
resolve(data);
} catch(err) {
reject(err);
}
})
}
在我的组件中
this.service.getData(url).then((response) => {
console.log(response);
}).catch(err => console.log(err))