I have a projectService to retrieve projects. Each project in turn has methods to retrieve corresponding tags and modules.
What i try to do is populate an instance property projects: Project[] with all corresponding tags and modules and when everything is populated call another method, which depends on the data.
I know my approach is not working because my map operation returns the projects before the observables in the for loop complete, but I have no idea how to do it properly.
class ProjectService {
getAll(): Observable<Project[]> {...}
}
class Project {
tagCollection: Tag[];
moduleCollection: Module[];
getTags(): Observable<Tag[]> {...}
getModules(): Observable<Module[]> {...}
}
this.projectService
.getAll()
.pipe(
map(projects => {
for (const project of projects) {
project.getTags().subscribe(tags => {
project.tagCollection = tags;
});
project.getModules().subscribe(modules => {
project.modules = modules;
});
}
return projects;
})
)
.subscribe(projects => {
this.projects = projects;
this.someOtherMethod();
});
I have tried both solutions and edited them to retain the project type and use a similar coding style. Both solutions work and seem to do the same thing in my project context. But I'm unsure which solution is better and if the edits i made are breaking any reactive best practices. Can someone elaborate on this?
this.projectService
.getAll()
.pipe(
switchMap(projects =>
combineLatest(
projects.map(project =>
combineLatest([project.getTags(), project.getModules()]).pipe(
map(([tags, modules]) => {
project.tagCollection = tags;
project.modules = modules;
return project;
})
)
)
)
)
)
.subscribe(projects => {
console.log(projects);
this.projects = projects;
this.someOtherMethod();
});
this.projectService
.getAll()
.pipe(
mergeMap(projects => from(projects)),
mergeMap(project =>
combineLatest([project.getTags(), project.getModules()]).pipe(
map(([tags, modules]) => {
project.tagCollection = tags;
project.modules = modules;
return project;
})
)
),
toArray()
)
.subscribe(projects => {
console.log(projects);
this.projects = projects;
this.someOtherMethod();
});
Would something like that work for you?
Might need some tweaking to fit your project, but it seems to be working.
this.projectService.getAll()
.pipe(
switchMap(projects => !projects.length
? of([])
: combineLatest(
projects.map(project => combineLatest([
project.getTags(),
project.getModules()
]).pipe(
map(([tags, modules]) => ({tags, modules})),
map(data => ({
...project,
tagCollection: data.tags,
moduleCollection: data.modules
})),
))
))
).subscribe(projects => {
this.projects = projects;
this.someOtherMethod();
});
There a many different ways to perform batch calls asynchronously.
So what I like to do is flatten my data as I believe it's easier to perform operations on a more flat structure. After I'm done performing operations on the objects I regroup/reassemble them using the toArray operator
If you have any question regarding the operators I used feel free to ask. I highly recommend you to play around with the different rxjs transformation operators .
Be aware my code below is definitely not the best solution, but it works fine!
Hope it works for you! StackBlitz
import { Component, Input, OnInit } from '@angular/core';
import { Observable, of, from, combineLatest } from 'rxjs'
import { delay, map, mergeMap,switchMap, toArray } from 'rxjs/operators';
@Component({
selector: 'hello',
template: `<h1>Hello {{name}}!</h1>`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent implements OnInit {
@Input() name: string;
data: Partial<Project>[] = [
{ name: 'foo'},
{ name: 'foo'}
]
ngOnInit() : void {
this.getProjects().pipe(
mergeMap(projects => from(projects)),
mergeMap((project) => {
const tag$ = this.getTagsByProjectName(project.name);
const module$ = this.getModuleByProjectName(project.name);
return combineLatest([tag$,module$]).pipe(map(([tag,module]) => ({...project, tag, module})))
}),
toArray()
).subscribe({
next: projects => {
console.log(projects);
this.name = JSON.stringify(projects);
}
})
}
getProjects() : Observable<Partial<Project>[]> {
return of(this.data).pipe(
delay(100)
)
}
getTagsByProjectName(name: string){
const tag = 'bar';
return of(tag).pipe(delay(100));
}
getModuleByProjectName(name: string){
const module = 'baz';
return of(module).pipe(delay(100));
}
}
interface Project {
name: string;
tag: string;
module: string;
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.