1

To be honest I am a total noob at NGRX and only limited experience in rxjs. But essentially I have code similar to this:

@Effect()
applyFilters = this.actions$.pipe(
ofType<ApplyFilters>(MarketplaceActions.ApplyFilters),
withLatestFrom(this.marketplaceStore.select(appliedFilters),
  this.marketplaceStore.select(catalogCourses)),
withLatestFrom(([action, filters, courses]) => {
  return [courses,
    this.combineFilters([
      this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.TRAINING_TYPE),
      this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.INDUSTRIES)
      ])
  ];
}),
map(([courses, filters]) => {
  console.log('[applyFilters effect] currently applied filters =>', filters);

  console.log('courseFilters', filters);
  const filteredCourses = (courses as ShareableCourse[]).filter(x => (filters as number[]).includes(+x.id));
  console.log('all', courses);
  console.log('filtered', filteredCourses);

  return new SetCatalogCourses(filteredCourses);
})
);

Helper method:

private combineFilters(observables: Observable<number[]>[]): number[] {
if (!observables.some(x => x)) {
  return [];
} else {
  let collection$ = (observables[0]);
  const result: number[] = [];

  for (let i = 0; i < observables.length; i++) {
    if (i >= 1) {
      collection$ = concat(collection$, observables[i]) as Observable<number[]>;
    }
  }

  collection$.subscribe((x: number[]) => x.forEach(y => result.push(y)));
  return result;
}

}

So essentially the store objects gets populated, I can get them. I know that the observables of 'this.getCourseIdsFromFiltersByFilterType(args)' do work as on the console log of the 'filters' they are there. But the timing of the operation is wrong. I have been reading up and am just lost after trying SwitchMap, MergeMap, Fork. Everything seems to look okay but when I am trying to actually traverse the collections for the result of the observables from the service they are not realized yet. I am willing to try anything but in the simplest form the problem is this:

Two observables need to be called either in similar order or pretty close. Their 'results' are of type number[]. A complex class collection that has a property of 'id' that this number[] should be able to include. This works just fine when all the results are not async or in a component.(I event dummied static values with variables to check my 'filter' then 'includes' logic and it works) But in NGRX I am kind of lost as it needs a return method and I am simply not good enough at rxjs to formulate a way to make it happy and ensure the observables are fully realized for their values from services to be used appropriately. Again I can see that my console log of 'filters' is there. Yet when I do a 'length' of it, it's always zero so I know somewhere there is a timing problem. Any help is much appreciated.

djangojazz
  • 14,131
  • 10
  • 56
  • 94
  • Is `getCourseIdsFromFiltersByFilterType` a method that calls some sort of possibly remote service which returns an `Observable` of courseIds? If. the previous is true, do you want to concatenate the results of the 2 calls you make? – Picci Apr 03 '20 at 06:25
  • @Picci Correct. Those methods get a number array and I would like to combine and ensure they are subscribed to for the end effect. – djangojazz Apr 03 '20 at 13:45

1 Answers1

1

If I understand the problem, you may want to try to substitute this

withLatestFrom(([action, filters, courses]) => {
  return [courses,
    this.combineFilters([
      this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.TRAINING_TYPE),
      this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.INDUSTRIES)
      ])
  ];
}),

with something like this

switchMap(([action, filters, courses]) => {
  return forkJoin(
     this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.TRAINING_TYPE),
     this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.INDUSTRIES
  ).pipe(
     map(([trainingFilters, industryFilters]) => {
        return [courses, [...trainingFilters, ...industryFilters]]
     })
}),

Now some explanations.

When you exit this

withLatestFrom(this.marketplaceStore.select(appliedFilters),
  this.marketplaceStore.select(catalogCourses)),

you pass to the next operator this array [action, filters, courses].

The next operator has to call some remote APIs and therefore has to create a new Observable. So you are in a situation when an upstream Observable notifies something which is taken by an operator which create a new Observable. Similar situations are where operators such as switchMap, mergeMap (aka flatMap), concatMap and exhastMap have to be used. Such operators flatten the inner Observable and return its result. This is the reason why I would use one of these flattening operators. Why switchMap in your case? It is not really a short story. Maybe reading this can cast some light.

Now let's look at the function passed to switchMap

return forkJoin(
         this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.TRAINING_TYPE),
         this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.INDUSTRIES
      ).pipe(
         map(([trainingFilters, industryFilters]) => {
            return [courses, [...trainingFilters, ...industryFilters]]
         })

This function first executes 2 remote API calls in parallel via forkJoin, then take the result of these 2 calls and map it to a new Array containing both courses and the concatenation of trainingFilters and industryFilters

Picci
  • 16,775
  • 13
  • 70
  • 113
  • Awesome, works perfectly thank you. The combo of SwitchMap with Forkjoin with a pipe into a map still eludes me 'getting' it fully. What you say makes sense but I am more of C# developer and just need to do it more to fully get 'when' to use the different methods you described like switchMap, mergeMap, and exhaustMap. Thanks! – djangojazz Apr 03 '20 at 17:00
  • You may [read this](https://medium.com/better-programming/rxjs-patterns-emerging-from-stackoverflow-asynchronous-api-calls-as-streams-in-the-real-world-ef636c9af19a) to see if you get a bit more clarity around Observables used with remote APIs – Picci Apr 03 '20 at 18:19