TheViking
TheViking

Reputation: 161

How to use combineLatest where one of the streams is dependent of one of the others?

I have an Angular-resolver that fetch data from the backend. I have the following calls to perform:

GetProject(projectId): Observable<IProject>
GetSites(projectId): Observable<ISites[]>
GetPersons(siteId): Observable<IPerson[]>

I'm trying to use combineLatest but not sure how to use RxJs in my scenario. I want all request to complete before resolving, but GetPersons() should have the id of the first item in GetSites() result as input. How is this done?

Upvotes: 3

Views: 1651

Answers (3)

MoxxiManagarm
MoxxiManagarm

Reputation: 9124

this.project$ = this.myService.getProject(projectId);
this.sites$ = this.myService.getSites(projectId);
this.persons$ = this.sites$.pipe(
  switchMap(
    (sites: ISites[]) => merge(...sites.map((site: ISites) => this.myService.getPersons(site.id))),
  ),
); // that should result in Observable<IPerson[][]>, you likely need to flatten it

Upvotes: 0

user4676340
user4676340

Reputation:

Create a replay subject :

const sub = new ReplaySubject(3);

Then make your calls

this.getProject(1).pipe(
  tap(project => sub.next(project)),
  switchMap(project => this.getSites(1)),
  tap(sites => sub.next(sites)),
  switchMap(sites => this.getPersons(sites[0].id)),
  tap(person => sub.next(person))
);

Your replay subject will contain the project as first value, the sites as second value, the person as thrid value.

You can do it with the combineLatest format with a BehaviorSubject.

const obs = new BehaviorSubject([]);
const add = val => obs.pipe(
  take(1),
  map(v => ([...v, val]))
).subscribe(v => obs.next(v));

this.getProject(1).pipe(
  tap(project => add(project)),
  switchMap(project => this.getSites(1)),
  tap(sites => add(sites)),
  switchMap(sites => this.getPersons(sites[0].id)),
  tap(person => add(person))
);

This time, the value returned will be an array of all of your values.

Finally, you have the complicated syntax to concatenate them, without a subject.

this.getProject(1).pipe(
  switchMap(project => this.getSites(1).pipe(map(sites => ([project, sites])))),
  switchMap(([project, sites]) => this.getPersons(sites[0].id).pipe(map(person => ([project, sites, map])))),
);

Upvotes: 2

martin
martin

Reputation: 96889

It looks more like you want to just concat several calls:

forkJoin([GetProject(projectId), GetSites(projectId)]).pipe(
  concatMap(([project, sites]) => {
    const siteId = /* whatever here */;
    return GetPersons(siteId);
  }),
).subscribe(...);

It also depends on whether you want to receive in the observer all responses or just the last one. If you want all responses then you'll need to chain GetPersons with map and append the first two responses:

GetPersons(siteId).pipe(
  map(persons => [project, sites, persons]),
)

Upvotes: 3

Related Questions