问题
Good morning to everyone. I'm a newbie of Angular2 and in a project I'm working on I have to make some HTTP requests in order to retrieve data from a REST backend.
From the backend I have to get a list of Modules. Each Module has one or more Actions. The typescript classes representing the two entities are the following
export class Module {
public name: string;
public htmlPath: string;
public actions: ModuleAction[];
constructor(data: IModule){
this.name = data.name;
this.actions = [];
this.htmlPath = data.htmlPath;
}
addAction(action: ModuleAction): void {
this.actions.push(action);
}
}
export class ModuleAction {
private name: string;
constructor(data: IModuleAction){
this.name = data.actionName;
}
}
The backed exposes a call to retrieve a list of modules without the relative actions and a second endpoint to retrieve a list of actions for a given module. In order to initialize my angular interface I have to make a first HTTP request to get the modules list. Once obtained the modules I have to make a second request for each one of them to retrieve the actions and update the Module instance.
Yesterday I tried to use RxJS to get the data. The only working solution I found has been the following
getModules(type?: string) : Observable<Module[]> {
let url = ...
console.log("getting modules");
return this.http.get(url)
.map(res => {
let body = res.json();
let modules = body.map(jsModule => {
let m = new Module(jsModule);
let url = HttpHandlerService.URL_INFO + '/' + m.htmlPath;
console.log("getting actions for module " + m.name);
this.http.get(url)
.map(response => {
let body = response.json();
return body.actions.map(function(a : IModuleAction){
return new ModuleAction(a);
});
})
.subscribe(actions => {
for(let a of actions)
m.addAction(a);
});
return m;
});
return modules;
})
.catch(this.handleError);
}
I'm quite sure that I'm wrongly using the RxJS operator and there must be a different solution to obtain the same result in a more "RxJS compliant" way.
How can I use RxJS operators in order to obtain the same result? Thanks a lot for your help.
回答1:
Chaining interdependent HTTP requests can be achieved using the Observable.flatMap
operator. See this post: Angular 2 - Chaining http requests
And, since you need to retrieve module actions for each module, use Observable.forkJoin
operator. See this post: Send multiple HTTP GET requests in parallel
The final structure would look something like:
getModules()
.flatMap(modules => {
this.modules = modules;
let moduleActionsObservables = modules.map(module => {
return this.getModuleActions(module);
});
return Observable.forkJoin(moduleActionsObservables);
})
.map(result) => {
// module actions for each module
console.log(result); // => [[ModuleAction, ModuleAction], [ModuleAction], [ModuleAction, ModuleAction]]
// if you want to assign them to their respective module, then:
this.modules.forEach((module, i) => {
module.actions = result[i];
});
return this.modules;
});
// The result:
this.getModules().subscribe(
modules => {
console.log(modules); // => [{moduleId: '123', actions: [..]}, {moduleId: '124', actions: [..]}]
});
回答2:
try to make some separations and use switchMap:
getModules(type?: string) : Observable<Module[]> {
let url = ...
console.log("getting modules");
return this.http.get(url)
.switchMap(res => {
let body = res.json();
return getJsModules(body);
})
.catch(this.handleError);
}
getJsModules(body){
return body.switchMap(jsModule => {
let m = new Module(jsModule);
let url = HttpHandlerService.URL_INFO + '/' + m.htmlPath;
console.log("getting actions for module " + m.name);
return getModuleActions.map(actions => {
for(let a of actions)
m.addAction(a);
});
});
}
getModuleActions(url){
return this.http.get()
.switchMap(response => {
let body = response.json();
return body.actions.map(function(a : IModuleAction){
return new ModuleAction(a);
});
})
}
回答3:
I would use flatMap and forkJoin:
this.http.get(url)
.flatMap(t=> {
let urls = getUrls(t.json())
.map(url=> this.http.get(url).map(t=>getActions(t.json()));
return Observable.forkJoin(urls).map(x=> [].concat(...x));
}
//.subscribe => [ModuleAction, ModuleAction, ModuleAction,...]
Where
getUrls(body:[]): string[] {
return body.map(jsModule => {
let m = new Module(jsModule);
let url = HttpHandlerService.URL_INFO + '/' + m.htmlPath;
return url;
};
}
getActions(body:any): ModuleAction[] {
return body.actions.map((a:IModuleAction) => new ModuleAction(a));
}
来源:https://stackoverflow.com/questions/42829329/angular2-rxjs-nested-http-calls