3

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?

TheViking
  • 153
  • 3
  • 15
  • `combineLatest` should be for hot observables. For cold ones, you should use `forkJoin`. Also, it's not suited for related observables. –  Oct 01 '19 at 11:27

3 Answers3

3

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]),
)
martin
  • 93,354
  • 25
  • 191
  • 226
  • I like this approach, but I need to create a resolved-object to return to the router. I want something like: return new ResolvedData(project, sites, persons); – TheViking Oct 01 '19 at 12:48
  • 1
    You can do that inside the `map` operator instead of returning an array. – martin Oct 01 '19 at 12:49
2

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])))),
);
0
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
MoxxiManagarm
  • 8,735
  • 3
  • 14
  • 43