Combining Firebase Queries with AngularFire2

Update:

The issue with encountering empty value fields was due to missing keys in the database, so much of the discussion here may not be relevant to your specific question. If you are seeking a way to merge queries in AngularFire2, the provided answer below does a solid job at accomplishing this. I am currently utilizing combineLatest instead of forkJoin. To implement this, make sure to include

import 'rxjs/add/observable/combineLatest';
.

My Firebase structure is denormalized as follows:

members
  -memberid1
    -threads
       -threadid1: true,
       -threadid3: true
    -username: "Adam"
    ...

threads
  -threadid1
      -author: memberid3
      -quality: 67
      -participants
         -memberid2: true,
         -memberid3: true
     ...

I aim to display the username in my threads view, which is sorted by quality.

Here is my service:

getUsername(memberKey: string) {
    return this.af.database.object('/members/' + memberKey + '/username')
}

getFeaturedThreads(): FirebaseListObservable<any[]> {
    return this.af.database.list('/threads', {
        query: {
            orderByChild: 'quality',
            endAt: 10
        }
    });
}

This is how my component is set up:

ngOnInit() {
    this.threads = this.featuredThreadsService.getFeaturedThreads()
    this.threads.subscribe( 
        allThreads => 
        allThreads.forEach(thisThread => {
            thisThread.username = this.featuredThreadsService.getUsername(thisThread.author)
            console.log(thisThread.username)
        })
    )
} 

Despite having implemented this setup, unfulfilled observables seem to be logged to the console for some reason.

https://i.sstatic.net/W8XlO.png

I am looking to assign these values to a property of threads so that it can be rendered in my view like this:

<div *ngFor="let thread of threads | async" class="thread-tile">
    ...
    {{threads.username}}
    ...
</div>

Update: Included console.log for allThreads and thisThread

https://i.sstatic.net/tpXeU.png

https://i.sstatic.net/iO408.png

Update: Subscribed to getUsername()

this.featuredThreadsService.getUsername(thisThread.author)
        .subscribe( username => console.log(username))

Resulting in objects without any values being displayed:

https://i.sstatic.net/Gi2dF.png

Answer №1

To create an observable based on the getFeaturedThreads function that retrieves information about members and updates the user names in each thread's participants property, follow these steps:

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/forkJoin';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/first';
import 'rxjs/add/operator/switchMap';

let featuredThreadsWithUserNames = this.getFeaturedThreads()

  // Switch to new member queries every time getFeaturedThreads emits:

  .switchMap(threads => {

    // Create an array of observables for updating user names in threads:

    let memberObservables = [];
    threads.forEach(thread => {

      // Update the author's name:

      memberObservables.push(this.af.database
        .object(`members/${thread.author}`)
        .first()
        .do(value => { thread.author = value.username; })
      );

      // Update the participants' names:

      Object.keys(thread.participants).forEach(key => {
        memberObservables.push(this.af.database
          .object(`members/${key}`)
          .first()
          .do(value => { thread.participants[key] = value.username; })
        );
      });
    });

    // Join the member observables to update the threads and return them:

    return Observable.forkJoin(...memberObservables, () => threads);
  });

This will generate an observable that emits whenever getFeaturedThreads emits but does not re-emit if user names change. To ensure re-emitting on name changes, replace forkJoin with combineLatest and remove the first operator from the member observables.

Answer №2

In order to efficiently handle joins between users, I created a specialized service that caches previously retrieved user data and seamlessly integrates it with the referencing information using minimal code. This service utilizes a nested map structure to facilitate the joining process:

constructor(public db: AngularFireDatabase, public users:UserProvider) {
    this.threads = db.list('threads').valueChanges().map(messages => {
      return threads.map((t:Message) => {
        t.user = users.load(t.userid);
        return m;
      });
    });
}

The UserProvider service implementation is structured as follows:

@Injectable()
export class UserProvider {
  db: AngularFireDatabase;
  users: Map<String, Observable<User>>;

  constructor(db: AngularFireDatabase) {
    this.db = db;
    this.users = new Map();
  }

  load(userid:string) : Observable<User> {
    if( !this.users.has(userid) ) {
      this.users.set(userid, this.db.object(`members/${userid}`).valueChanges());
    }
    return this.users.get(userid);
  }
}

A comprehensive demo showcasing the functionality of the joins along with necessary boilerplate can be found here.

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

How to eliminate the ng-component tag from an Angular 8 table row template

I currently have a dynamic table component with 2 modes: Normal table - functioning properly Custom Row Template - encountering issues due to Angular adding the <ng-component> tag The logic within the TableComponent is not the main concern, it&apo ...

Instructions on resolving the issue: The type 'string | ChatCompletionContentPart[] | null' cannot be assigned to type 'ReactNode'

I've been working on my first Saas App, similar to a ChatGPT, using NextJs with the OpenAI Api. Most of the development was based on a YouTube tutorial until I encountered two errors caused by an update in the OpenAI version. Despite trying various so ...

Values within the inner *ngFor loop have been selected from the outer *ngFor loop

In our Angular 4 application, we are facing a specific requirement that needs to be addressed. We have successfully implemented *ngFor up to three levels and below is the code snippet for the implementation: <div *ngFor="let country of Countries"> ...

Tips for looping through an array of objects in Angular 9 and adjusting the time if the <mat-checkbox> is selected

I am currently working with an array of objects that are displayed in a <mat-table>. Each object has a property called sync, which is represented by a <mat-checkbox>. My goal is to create functionality where checking the box and then pressing ...

Make sure to wait for the observable to provide a value before proceeding with any operations involving the variable

Issue with handling observables: someObservable$.subscribe(response => this.ref = response); if (this.ref) { // do something with this.ref value } ERROR: this.ref is undefined How can I ensure that the code relying on this.ref only runs after ...

How to use TypeScript to modify button styling with an OnClick() event handler

Learning TypeScript with Existing Code Transition Currently, I am delving into the world of TypeScript and in the process of converting my CoffeeScript code to TypeScript (specifically Lit Web Component). Confusion on Translation Process I'm encount ...

Utilizing ng-bootstrap within a rebranded module

I'm facing an issue with my nav-bar module that utilizes ng-bootstrap: import {NgModule, NgZone} from '@angular/core'; import { CommonModule } from '@angular/common'; import {NavigationComponent} from "./components/navigation/navi ...

How can we pass the onClick prop from a child component to a parent component in React with Typescript?

Currently, I am utilizing React along with TypeScript. I am curious about the process of passing an event from the parent component to a child component using props. Here is an example for better understanding: parent.tsx const ParentComponent: React.F ...

Navigating through a dictionary in React TypescriptWould you like to learn

Currently, I am delving into the world of React and TypeScript. Within my journey, I have stumbled upon a dictionary representing various departments, with employee data stored in arrays. type Department = { Emp_Id: number, Name: string, Age: n ...

Angular 2 regex for validating FQDN/URL formats

As a beginner in regex, I'm looking for help with validating fully qualified domain names (e.g. mysite.com) or a URL without checking the protocol. Can anyone provide a regex expression or suggest where to find one? ...

Output the current screen to the console using the Stack.Navigator component in React Native

Trying to find a way to automatically log which screen is being displayed on each view when using the Stack.Navigator component in React Native with TypeScript. Adding console logs manually to every screen is too time-consuming for me. I am considering im ...

Is it possible to execute user-defined functions dynamically in a Node.js application without having to restart the server

I am exploring the potential for allowing users to insert their own code into a Node application that is running an express server. Here's the scenario: A user clicks 'save' on a form and wants to perform custom business validations. This ...

What strategies work well for managing Firestore integration within a React application?

Primarily, I have expertise as a react developer and my knowledge of the back-end is limited to just the basics. In an attempt to create a react project with Firebase using Firestore as the database, I typically rely on either redux or react-query to sto ...

Tips for determining the minimum value within an array of objects across multiple keys using a single function

I am currently tasked with the challenge of determining the minimum value from an array of objects that contain multiple keys. My ultimate goal is to identify the minimum value among all keys or specific keys within the objects. For instance var users = ...

Angular ngFor does not display the JSON response from the API (GET request)

I'm attempting to retrieve a JSON response from my API and display the values on my Angular page using ngFor. Although I don't have any build errors, the values are not being displayed on the page. They only appear in the console when using cons ...

Lazy loading routes in Angular 2 that include auxiliary router outlets

I'm currently facing an obstacle with my routing setup and could use some assistance. Previously, all my routes were organized in a single app.routing.ts file. However, I am now restructuring my routes into separate files to be lazily loaded along wit ...

Type errors in NextJS are not being displayed when running `npm run dev`

When encountering a typescript error, I notice that it is visible in my editor, but not in the browser or the terminal running npm run dev. However, the error does show up when I run npm run build. Is there a method to display type errors during npm run d ...

Is there a way to seamlessly transfer (optional) parameters from a CloudFormation template to a CDK resource within a CfnInclude without statically defining the list of parameters?

Trying to grasp these syntax rules, unsure if it's possible. We have numerous CloudFormation templates that we want to deploy using CDK by constructing them with CfnInclude. The issue is that CfnInclude always needs an explicit parameters argument if ...

Encountering compilation errors with TypeScript files in Angular 2 while using Visual Studio 2017

Currently, I am in the process of developing an Angular 2 application Here is a snippet of the TypeScript code that I have written: import { Component } from 'angular2/core'; @Component({ selector: 'my-app', template: ' &l ...

Converting a Typescript generic type `{[key: string]: T}` into an object literal

I am seeking to create a function that ensures an object adheres to the type {[key: string]: T} and outputs an object literal based on the input argument. For instance, let's say I have the following type A: interface A{ a: string, b: number } I ...