What is the best way to merge Observable type arrays into one Observable<T[]>?

I am currently running multiple queries on Firebase Firestore, each of which has the potential to return an Observable. My goal is to combine all these Observables into a single one.

Despite experimenting with various RxJS operators such as combineLatest, forkJoin, concat, zip, merge, and mergeMap, I have not yet been successful in achieving the desired outcome.

getPrivateBooksFromAuthors(authors): Observable<Book[]> {
    let bookRefs: Observable<Book[]>[] = [];
    authors.map(key => {
      bookRefs.push(this.afs.collection<Book>('books', ref => 
        ref.where('public', '==', false)
           .where('authors', 'array-contains', key)
        ).valueChanges())
    });
    return bookRefs[0]
}

In the provided code snippet, I am retrieving private books from authors[0]. Despite attempting to use concat(...bookRefs) or concat(bookRefs[0], bookRefs[1]), I continue to only receive books from authors[0]. My expectation is to obtain all the books from all the provided authors.

Answer №1

In agreement with @Doflamingo's suggestion, you can utilize forkJoin to simultaneously call multiple functions and receive a consolidated response as an array. The issue arises when each response is in the form of a Book[], resulting in forkJoin returning an array of Book[] (Book[][]). To resolve this, you must flatten the Book[][] into a single Book[]. This can be achieved by using a combination of mapping and a reduce function.

I have provided a straightforward code snippet below that showcases the console logging of the forkJoin response and the outcome after applying the reduce function.

function mockBooks$(key): Observable<Book[]> {
  return rxjs.of([key + '-book1', key + '-book2', key + '-book3']);
}

function getPrivateBooksFromAuthors(authors): Observable<Book[]> {
    let bookRefs: Observable<Book[]>[] = authors.map(key => mockBooks$(key));

    return rxjs.forkJoin(bookRefs).pipe(
        rxjs.operators.tap((books) => console.log('After forkJoin', books)),
        // Flattening operation required here!
        rxjs.operators.map(books => books.reduce((acc, cur) => [...acc, ...cur], []) ));
}


const authors = ['a1', 'a2', 'a3', 'a4', 'a5'];
getPrivateBooksFromAuthors(authors).subscribe((data) => console.log('Final result', data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.js"></script>

Hoping this explanation proves beneficial!

Answer №2

In my opinion, the most effective solution is to utilize fork join.

Using Fork allows you to make the calls in parallel. The outcome of each call is stored in one object (join) from which you can retrieve all the results.

Answer №3

After receiving a helpful suggestion from @Doflamingo regarding using forkJoin, I was inspired by @Llorenç's code example showcasing the map and reduce pipes to merge Observables into one. However, I encountered issues with forkJoin if certain authors did not have any associated books. In the end, I opted for combineLatest as it does not require all Observables to be completed before emitting values. Here is my modified code snippet (all credit goes to @Llorenç):

  mockBooks$(key): Observable<Book[]> {
    return this.afs.collection<Book>('books', ref => 
      ref.where('private', '==', true)
         .where('authors', 'array-contains', key)
      ).valueChanges()
    // return of([key + '-book1', key + '-book2', key + '-book3']);
  }

  getPrivateBooksFromAuthors(authors): Observable<Book[]> {
    let bookRefs: Observable<Book[]>[] = authors.map(key => this.mockBooks$(key));

     // return combineLatest(bookRefs).pipe(
     //     tap((books) => console.log('After forkJoin', books)),
     //     // You need this flattening operation!!
     //     map(books => books.reduce((acc, cur) => [...acc, ...cur], []) ));

     return combineLatest<Book[]>(bookRefs).pipe(
        map(arr => arr.reduce((acc, cur) => acc.concat(cur) ) ),
     )
  }

I've left out Llorenç's original code for comparison purposes. Many thanks to both @Doflamingo and @Llorenç for their invaluable contributions!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Enhance Firebase Security Measures: Introduce personalized User Identification in request.auth without relying on Firebase Authentication

I've come across an issue while using next-auth v4, Firebase v9, and NextJS in my project. The problem lies with the Firebase security rules not receiving anything in request.auth due to the way I'm using next-auth. I haven't been able to fi ...

Using Regular Expressions: Ensuring that a specific character is immediately followed by one or multiple digits

Check out the regular expression I created: ^[0-9\(\)\*\+\/\-\sd]*$ This regex is designed to match patterns such as: '2d6', '(3d6) + 3', and more. However, it also mistakenly matches: '3d&apos ...

Angular 5 allows users to easily add products to their cart by specifying the quantity, while also displaying the total

In my Angular 5 application, I have implemented a feature where users can select the quantity of products and the total price is calculated based on the selected quantity. However, there is a limit on the quantity that can be added for each product. For ex ...

Retrieve the JSON information specifically for the selected ID

Having a set of JSON data, with 2 base IDs and attachment data within each ID. The goal is to click on a base ID and display its attachment data on a separate page. How can I retrieve information for the clicked ID only? { "_embedded": { "deli ...

The setupFile defined in Jest's setupFilesAfterEnv configuration is not recognized by the VSCode IDE unless the editor is actively open

I've put together a simplified repository where you can find the issue replicated. Feel free to give it a try: https://github.com/Danielvandervelden/jest-error-minimal-repro Disclaimer: I have only tested this in VSCode. I'm encountering diffic ...

Utilizing Print Styles in an Angular 7 Application: A Step-by-Step Guide

I'm trying to print an html form within my Angular7 App with minimal margins. I've attempted different solutions such as adjusting the margins manually in Chrome's print preview box and adding @media print styles & @page styles in both the c ...

Executing a series of asynchronous HTTP calls in Angular until a specific condition is satisfied

In Angular, I am making an HTTP call that returns a promise. Currently, I am refreshing the call using setTimeout at regular intervals. Are there any built-in functions or design patterns available to handle this task more efficiently? ...

Maximizing the efficiency of enums in a React TypeScript application

In my React application, I have a boolean called 'isValid' set like this: const isValid = response.headers.get('Content-Type')?.includes('application/json'); To enhance it, I would like to introduce some enums: export enum Re ...

Achieve a sticky y-axis while scrolling horizontally with chartJS and Angular

Is there a way to keep the y-axis fixed while scrolling horizontally? Below is an example that achieves this functionality without Angular: $(document).ready(function () { function generateLabels() { var chartLabels = []; for (x = ...

firebase-functions: Error status not recognized: TypeError: db.collectionGroup does not exist as a function

After implementing a collectionGroup query in my "firebase-functions" code, I encountered an error stating: "Unknown error status: TypeError: db.collectionGroup is not a function". What could be causing this issue? I have recently updated my node.js, fire ...

Use the loopback repository from one controller in a different controller

When I try to access the 'productRepository' property in the test.controller.ts file, an error occurs. import {repository} from '@loopback/repository'; import {ProductRepository} from '../repositories'; export class TestContro ...

Unable to iterate over property in Angular 6 after receiving response

Recently, I started working with Angular and encountered an issue while trying to loop through a property in the component file. I kept receiving an error message stating that the property length is undefined. Check out this screenshot from Chrome DevTool ...

Subscribe is triggering even when the data in the store remains unchanged

I am facing an issue with my Angular 5 app that utilizes ngrx. In the store, I have an array of objects. The problem arises when I subscribe to receive changes in this array, even though I haven't made any changes to state.details.selectedOptions. It ...

Can anyone assist me with creating a reusable component for `next-intl` that can be

Let me explain what I'm looking for. If you're not familiar with next-intl, it's a package designed to provide internationalization support for Next.js applications. The Issue: The developer of next-intl is working on a beta version that su ...

The ReplaySubject is receiving an updated response, however, the View is displaying the same information

Having an issue with the ReplaySubject in my code. It seems that after updating an Array, the new values are not reflecting on the UI. I can only see the old values unless I reload the page. I have tried using ngZone but it didn't help. The code is d ...

Anticipating a service that is dependent on multiple layers of promises and observables

My current project involves implementing the Ionic Native QR Scanner. Since I anticipate using the scanner across multiple modules, I decided to create a straightforward service that can launch the scanner and provide the result. Initially, I am following ...

Loading an external javascript file dynamically within an Angular component

Currently, I'm in the process of developing an Angular application with Angular 4 and CLI. One of my challenges is integrating the SkyScanner search widget into a specific component. For reference, you can check out this Skyscanner Widget Example. T ...

Bootstrap causing issues with correct display of div element

I'm having trouble getting my divs to display on the same row. It seems like when I have 2 rows, the button is positioned correctly. However, with only one row, the button ends up on the second row. Here is the current layout: https://i.sstatic.net/L ...

A special function designed to accept and return a specific type as its parameter and return value

I am attempting to develop a function that encapsulates a function with either the type GetStaticProps or GetServerSideProps, and returns a function of the same type wrapping the input function. The goal is for the wrapper to have knowledge of what it is ...

Struggling to increment the badge counter in a React Typescript project

I'm a bit unsure about where to apply the count in my Badge Component. The displayValue should include the count, but I'm struggling with how to implement it. UPDATE: The counter is now in place, but when I decrease the count and it reaches 0, t ...