33

I have the following code:

//Loop: For each user ID/Role ID, get the data
userMeta.forEach((businessRole) => {
  Observable.forkJoin(
    af.database.object('/roles/'+businessRole.$value),
    af.database.object('/users/'+businessRole.$key)
  ).subscribe(
    data => {
      console.log("Data received");
      data[1].role = data[0];
      this.users.push(data[1]);
    },
    err => console.error(err)
  );

I am trying to subscribe to a result of 2 observables using forkJoin.

For some reasons, the "Data received" message is not shown.

My userMeta variables looks fine at console.log:

enter image description here

What's wrong?

Update: the following code does not return anything either

let source = Observable.forkJoin(
        af.database.object('/roles/'+businessRole.$value),
        af.database.object('/users/'+businessRole.$key)
    );
    let subscription = source.subscribe(
      function (x) {
    console.log("GOT: " + x);
  },
  function (err) {
    console.log('Error: %s', err);
  },
  function () {
    console.log('Completed');
  });

What I actually trying to do is improve the performance of the following code:

//Subscription 3: role ID to role Name
        af.database.object('/roles/'+businessRole.$value)
        .subscribe((roleData) => {
        //Subscription 4: Get user info
        af.database.object('/users/'+businessRole.$key).subscribe(user => {
martin
  • 93,354
  • 25
  • 191
  • 226
TheUnreal
  • 23,434
  • 46
  • 157
  • 277
  • `forkJoin()` emits a value after both Observables complete, so are you sure they do? Maybe one of them end with an error... – martin Oct 28 '16 at 12:23
  • 1
    No errors too (see question update). They also must work because the former code was a subscribe inside a subscribe, and it worked – TheUnreal Oct 28 '16 at 12:30
  • `forkJoin()` doesn't pass errors from source Observables so this won't print anything even if it threw errors. If you want to make sure it doesn't emit errors you need to subscribe to each of the source Observables. – martin Oct 28 '16 at 12:45

5 Answers5

33

forkJoin() requires all source Observables to emit at least once and to complete.

This following demo completes as expected:

const source = forkJoin(
  from([1,2,3]),
  from([9,8,7,6])
).subscribe(
  x => console.log('GOT:', x),
  err => console.log('Error:', err),
  () => console.log('Completed')
);

Live demo: https://stackblitz.com/edit/rxjs-urhkni

GOT: 3,6
Completed

Jan 2019: Updated for RxJS 6

martin
  • 93,354
  • 25
  • 191
  • 226
  • Did you checked my updated code in the end of question? because it works with the same observables, just not work `forkJoin()`. It's really strange... – TheUnreal Oct 28 '16 at 19:09
  • 2
    @TheUnreal But that's not the same as what you want to do with `forkJoin()`. `.subscribe((roleData) => {...` emits a value on every `next` call. `forkJoin()`. **forkJoin() requires both Observables to complete.** otherwise it doesn't emit anything. – martin Oct 28 '16 at 19:12
  • Well it seem like I miss-used `forkJoin()`. Because your answer is corrcet, I'll approve it - and open a new question about my case – TheUnreal Oct 28 '16 at 19:16
  • 14
    `Observable.combineLatest()` might be what you're looking for @TheUnreal. – Jakub Barczyk Oct 16 '17 at 07:12
  • @JakubBarczyk Thank you. If I could upvote you twice, I would. – David Morton Jul 20 '18 at 17:15
  • A clear example of `forkJoin` which details the `complete` method, great stuff – Drenai Oct 10 '18 at 07:59
29

Just add observer.complete();

Will not work:

observer.next(...)

Will work:

observer.next(...);
observer.complete();

Hope it helps.

Itay Ben Shmuel
  • 648
  • 7
  • 9
20

I've faced a similar issue: I was creating a list of observables dynamically and I've noticed that forkjoin() never emits nor completes if the list of observables is empty whereas Promise.all() resolves with an empty list :

Observable.forkJoin([])
    .subscribe(() => console.log('do something here')); // This is never called

The workaround I've found is to check the length of the list and to not use this operator when it is empty.

return jobList.length ? Observable.forkJoin(jobList) : Observable.of([]);
bgondy
  • 1,198
  • 3
  • 20
  • 32
  • I have eventemitters for child components that fire dynamically. I am binding it to an output parameter. I have attached it to the parent and need to wait till all the observables complete. – chris_r Aug 28 '18 at 20:54
16

I had a similar issue using Angular 2 / Angularfire 2, specifically where I was looking up whether users existed by email. In one case, the user exists and I received an array of one object from the Observable. In the other case, the user did not exist, and I received an empty array.

When I used forkJoin with a resultSelector and a subscribe, neither the resultSelector nor the subscribe function ever ran. However, when I tried

Observable.zip(
  FirebaseListObservable,
  FirebaseListObservable,
  (...results) => {
    return results.map(some code here)
  }
).subscribe(res => console.log(res));

Both the selector and the subscribe worked. I assume this has to do with @martin's answer, where forkJoin requires the observables to complete, because by definition it returns the the last emissions. If an observable never completes, I suppose it can never have a last emission.

Perhaps angularfire list observables (or object observables in your case) never complete, making the use of forkJoin impossible. Fortunately zip has similar behavior and still works, the difference being that it can repeat several times if the data changes in Firebase, where as forkJoin only combines the last response.

In my case, I'm looking at either 1) using zip and accepting that my code might run multiple times if user data changes while the .zip is still running, 2) manually disable the zip after the first set of data returns, or 3) ditch Angularfire and try out the Firebase api directly, using something like .once to see if I can get an observable that completes and triggers forkJoin.

ansorensen
  • 1,276
  • 1
  • 14
  • 29
  • 8
    i had exactly the same issue, which could be fixed quite simple by using `Observable.combineLatest` instead of `Observable.forkJoin`. Credits go to https://github.com/angular/angularfire2/issues/617 – djnose Feb 21 '18 at 10:30
  • Thanks for sharing this, it's actually very useful. – Becario Senior Aug 30 '18 at 12:09
  • Thank you, this was extremely helpful. I was doing a query on Firestore that may or may not return a value and when their was no value forkJoin would not return anything. I simply replaced forkJoin with combineLatest and it worked perfectly. Thank you for saving another hour of research. – Steve Klock Sep 16 '20 at 19:20
9

I had the same issue and I could not really get the forkJoin operator work, so I just used the combineLatest, which has worked!

Dávid Konkoly
  • 1,853
  • 1
  • 13
  • 8