0

I believe the following code can be refactored using a flatMap but I cant seem to get it working as desired.

I understand flatMap essentially maps and then flattens, which is perfect for me as I'm using forkJoin so get an array of responses back from getAutocompleteSuggestions().

I want a single array of results upon subscription (which is what the code below produces), but changing the top level map to flatMap sends multiple single objects to the subscription. How can this code be better written with flatMap()?

   const $resultsObservable: Observable<any> = Observable.of(query)
          .switchMap(q => this.getAutocompleteSuggestions(q))
          //tried changing the below to flatMap()
          .map(res => { 
            return res.map(resp => {
              const content = resp.content;
              content.map(result => this.someMethod(result));
              return content;
            })
            .reduce((flatArr, subArray) => flatArr.concat(subArray), []);
          });



  getAutocompleteSuggestions(query: string): Observable<any> {
    const subs$ = [];
    //... add some observables to $subs
    return Observable.forkJoin(...subs$);
  }
Ben Taliadoros
  • 7,003
  • 15
  • 60
  • 97
  • The `content.map(…)` line in there is either a bug or unclear. Since you don't use its result, `someMethod` is executed, but is not the result returned in the observable chain. If that's intended, you should run it in a side effect (`do`) on observable level and using `forEach` on the array level. `map` (on both levels) is there to map data, not to execute side effects. – Ingo Bürk Mar 09 '18 at 21:46
  • resp.content is an array, so im transforming that and then returning it, essentially discarding everything else in resp, and transforming the data i need to – Ben Taliadoros Mar 12 '18 at 14:35
  • `map` doesn't modify an array in-place. – Ingo Bürk Mar 12 '18 at 16:02
  • Sure, but the variables (result) being passed in are passed by reference, objects that are modified and stay modified in the (passed by reference) array – Ben Taliadoros Mar 13 '18 at 11:28
  • Then you should use `forEach`. `map` is not for side effects. The code may work, it's just badly designed and breaks the principle of least surprise. – Ingo Bürk Mar 13 '18 at 16:18
  • but I want the value of content to replace each res. returning to foreach doesnt manipulate the data – Ben Taliadoros Mar 13 '18 at 16:45

1 Answers1

1

It looks like there might be a bit of confusion between the RxJS flatMap and the Array prototype method flatMap. Note that the purpose of the RxJS flatMap is not to flatten arrays that are the subjects of the stream, but rather to flatten a stream of Obervables into a single observables. See this SO post:

Why do we need to use flatMap?

... Basically if Observable denotes an observable object which pushes values of type T, then flatMap takes a function of type T' -> Observable as its argument, and returns Observable. map takes a function of type T' -> T and returns Observable.

If you want your code to be a bit cleaner, you could use the myArray.flatMap method. Here's a potential answer to your question with the Array flatMap method:

const $resultsObservable: Observable<any> = Observable.of(query)
  .switchMap(q => this.getAutocompleteSuggestions(q))
  // Flatmap on the array here because response is an array of arrays.
  // The RxJS flatMap does not deal with flattening arrays
  .map(res => res.flatMap(resp => {
    const content = resp.content;
    content.map(result => this.someMethod(result));
    return content;
  }));

getAutocompleteSuggestions(query: string): Observable < any > {
  const subs$ = [];
  //... add some observables to $subs
  return Observable.forkJoin(...subs$);
}
Elliot Plant
  • 668
  • 6
  • 12
  • On this line: .map(res => res.flatMap(resp => { res is an array, so I get an error, "res.flatMap is not a function" – Ben Taliadoros Mar 12 '18 at 14:38
  • 1
    Apologies, the `flatMap` method is not part of the javascript spec - it has only been proposed and is [not supported by any browsers yet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap). Looks like you may have to use a combination of `map` and a flatten method of your own. Or you could try using a library like [lodash](https://lodash.com/) or [underscore](http://underscorejs.org/). – Elliot Plant Mar 12 '18 at 19:41