41

I'm trying to use forkJoin on two Observables. One of them starts as a stream... If I subscribe to them directly I get a response, forkJoin isn't firing though. Any ideas?

private data$: Observable<any[]>;
private statuses$: Observable<any[]>;
private queryStream = new Subject<string>();    

....

this.data$ = this.queryStream
    .startWith('')
     .flatMap(queryInput => {
            this.query = queryInput
            return this._companyService.getCompanies(this.queryRequired + ' ' + this.query, this.page, this.sort);
                })
            .share();
    
...

Observable.forkJoin(this.statuses$, this.companies$)
            .subscribe(res => {
                console.log('forkjoin');
                this._countStatus(res[0], res[1]);
            });


// This shows arrays in the console...

this.statuses$.subscribe(res => console.log(res));
this.companies$.subscribe(res => console.log(res));

// In the console
Array[9]
Array[6]
BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
nick
  • 3,521
  • 3
  • 22
  • 32

7 Answers7

53

forkJoin emits only when all inner observables have completed. If you need an equivalent of forkJoin that just listens to a single emission from each source, use combineLatest + take(1)

combineLatest(
  this.statuses$,
  this.companies$,
)
.pipe(
  take(1),
)
.subscribe(([statuses, companies]) => {
  console.log('forkjoin');
  this._countStatus(statuses, companies);
});

As soon as both sources emit, combineLatest will emit and take(1) will unsubscribe immediately after that.

user776686
  • 7,933
  • 14
  • 71
  • 124
  • 2
    The problem with combineLatest is the fact that it returns data for the observable that fired first. I had to change to forkjoin because I need to wait for both observables to wait on one another. – David Aguirre Jun 25 '20 at 16:08
  • For anyone looking for a solution that only emits when all observables inside `combineLatest` have emitted at least once, use `skipWhile` or `filter` to prevent `combineLatest` from emitting until they all pass the provided predicate function. – Damian Rivas Apr 30 '23 at 04:43
  • I tried to set of() in forkJoin's parameter but it still did not emit the subscribe() – aj go Jun 22 '23 at 21:45
22

A very common problem with forkJoin is that it requires all source Observables to emit at least one item and all of them have to complete.

In other words if this.statuses$ or this.companies$ doesn't emit any item and until they both complete the forkJoin won't emit anything.

this.statuses$.subscribe(
    res => console.log(res),
    undefined,
    () => console.log('completed'),
);
martin
  • 93,354
  • 25
  • 191
  • 226
  • 1
    They both emit items, if I subscribe and console log I can see the arrays they emit. I've updated the question to reflect this. Thanks – nick Mar 15 '17 at 12:45
  • 1
    @nick And do both of them complete? – martin Mar 15 '17 at 12:46
  • I'm not sure, what's the best way to tell? And if not, how do I make them complete? The issue seems to be with the `queryStream: Subject`, I just create a straight `Observable` it works... – nick Mar 15 '17 at 12:47
  • 4
    `Subject` doesn't complete until you tell it to do so. So you need to call `queryStream.complete()` manually. But this depends on your application logic, maybe you could use just `take(1)` without completing the `Subject`. See the update how to check the Observable completes – martin Mar 15 '17 at 12:50
6

The forkJoin didn't work so I used the below code to solve my problem. With mergeMap you can map the result of the outer subscription to the inner subscription and subscribe to it as you wish

this.statuses$.pipe(
    mergeMap(source => this.companies$.pipe(
        map(inner => [source , inner])
        )
    )
).subscribe(([e , r]) => {
    console.log(e , r);
})
dlsso
  • 7,209
  • 1
  • 21
  • 31
Richie50
  • 71
  • 1
  • 7
4

Appending .pipe(take(1)) as a pipe for asObservable()-like observables will do the job.

forkJoin({
    l0: this._svc.data$.pipe(take(1)),
    l1: this._api.getLogman1(),
    l2: this._api.getLogman2(),
    l3: this._api.getLogman3(),
})
    .pipe(
        takeUntil(this._unsubscribeAll),
    )
    .subscribe(x => {
        console.log(x);
    });
HoseinGhanbari
  • 1,058
  • 1
  • 11
  • 23
2

For me the combineLatest operator was the solution!

Dávid Konkoly
  • 1,853
  • 1
  • 13
  • 8
1
Observable.forkJoin([
      _someService.getUsers(),
      _someService.getCustomers(),
    ])
      .subscribe((data: [Array<User>, Array<Customer>]) => {
        let users: Array<User> = data[0];
        let customer: Array<Customer> = data[1];
      }, err => {
      });





      //someService
        getUsers():Observable<User> {
          let url = '/users';
          return this._http.get(url, headers)
            .map(res => res.json());
        }

        getCustomers():Observable<Customer> {
          let url = '/customers';
          return this._http.get(url, headers)
            .map(res => res.json());
        }
Yoav Schniederman
  • 5,253
  • 3
  • 28
  • 32
1

As mentionned on other responses forkJoin seems to not work because it does not have the same function as subscribe.

Subscribe will be triggered when it receives a next or completed event but forkJoin will only fire when all the observable are completed.

The difference with combineLatest is that it will be triggered if your observable send all a next or completed event and forkJoin will only trigger if your Observable send all a completed event.

Therefore your code will not work if your event send next event but this code should work :

combineLatest([this.statuses$, this.companies$])
        .subscribe(res => {
            console.log('combineLatest');
            this._countStatus(res[0], res[1]);
        });

You can see in the documentation of rxjs that in your case combineLastest is the best answer : https://www.learnrxjs.io/learn-rxjs/operators/combination/forkjoin

Doc to combineLatest https://www.learnrxjs.io/learn-rxjs/operators/combination/combinelatest

Doc to Observable and the different event that you could trigger : https://rxjs.dev/guide/observable

Allenile
  • 300
  • 3
  • 10