I am looking to set the information stored in a variable that contains an array from a Firebase subscription

In need of assigning the content of a variable filled with an array from a Firebase subscription, I encountered an issue where I couldn't access the value outside of the subscription in my component class. While I can use the created variable inside the .html file, it's crucial for me to have access to it within the component class as well. (Please note: these files were generated using the Angular Schematics "ng generate @angular/material:table")

//table-datasource.ts
import { DataSource } from '@angular/cdk/collections';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { map } from 'rxjs/operators';
import { Observable, of as observableOf, merge } from 'rxjs';
import { AuthService } from "../../services/auth.service";

// Define data model type
export interface TableItem {
  email: string;
  displayName: string;
  photoURL: string;
  emailVerified: boolean;
}

const EXAMPLE_DATA: TableItem[] = [ // Hardcoded data is visible but needs to be dynamic
  {...},
];

/**
 * Data source for the Table view. This class should
 * encapsulate all logic for fetching and manipulating the displayed data
 * (including sorting, pagination, and filtering).
 */
export class TableDataSource extends DataSource<TableItem> {
  //data: TableItem[] = EXAMPLE_DATA; //works with hardcoded data
  data: TableItem[] = []; // Need data to be the result of the subscription

  constructor(public authService: AuthService) {
    super();
    this.getUsersData();
    this.printUserData(); // Works only with hardcoded EXAMPLE_DATA, else shows empty Array
  }

  printUserData() {
    console.log("this.data", this.data);
  }

  getUsersData() {
    this.authService
      .getCollection("users")
      .subscribe((res: any[]) => {
        this.data = res; // Assigning values from subscription to this.data
        console.log("table-datasource", this.data); // Works here only
      })
  }
 ...
}

//table.component.ts
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTable } from '@angular/material/table';
import { AuthService } from "../../services/auth.service";
import { TableDataSource, TableItem } from './table-datasource';

@Component({
  selector: 'app-table',
  templateUrl: './table.component.html',
  styleUrls: ['./table.component.scss']
})
export class TableComponent implements AfterViewInit {
  @ViewChild(MatPaginator) paginator!: MatPaginator;
  @ViewChild(MatSort) sort!: MatSort;
  @ViewChild(MatTable) table!: MatTable<TableItem>;
  dataSource: TableDataSource;

  /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
  displayedColumns = ['id', 'name'];

  constructor(public authService: AuthService) {
    this.dataSource = new TableDataSource(authService);
  }

  ngAfterViewInit(): void { 
    this.dataSource.sort = this.sort;
    this.dataSource.paginator = this.paginator;
    this.table.dataSource = this.dataSource;
  }
}
//table.component.html
<div class="mat-elevation-z8">
  <table mat-table class="full-width-table" matSort aria-label="Elements">
    <!-- Id Column -->
    <ng-container matColumnDef="id">
      <th mat-header-cell *matHeaderCellDef mat-sort-header>Id</th>
      <td mat-cell *matCellDef="let row ">{{row.uid}}</td>
    </ng-container>

    <!-- Name Column -->
    <ng-container matColumnDef="name">
      <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
      <td mat-cell *matCellDef="let row">{{row.email}}</td>
    </ng-container>

    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
    <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
  </table>

  <mat-paginator #paginator
      [length]="dataSource?.data?.length"
      [pageIndex]="0"
      [pageSize]="10"
      [pageSizeOptions]="[5, 10, 20]"
      aria-label="Select page">
  </mat-paginator>
</div>

Answer №1

Remember, API calls require time to complete. It's important to wait for the data before attempting to display it.

Consider this example:

  getUsers2() {
    this.authService
      .getCollection2("users")
      .subscribe((res: any[]) => {
        this.data = res //this assigns values from res to this.data
        this.printUser();
      })
  }

Javascript doesn't halt execution for subscriptions; instead, it continues with other code while waiting for responses, making asynchronous calls non-blocking. It checks for responses in a queue and executes them when available.

To learn more about how asynchronous code works in Javascript, visit here.


The recommended approach after a subscription callback is to define a function and call it at the end of the subscription:

  getUsers2() {
    this.authService
      .getCollection2("users")
      .subscribe((res: any[]) => {
        this.data = res //this assigns values from res to this.data
        this.doWhateverWithMyData();
      })
  }

If you need to block code execution until a response is received, you can use async / await. Use RXJS' firstValueFrom() to get a Promise that resolves upon receiving a response

  constructor(public authService: AuthService) {
    super();
  }

  async ngOnInit() {
    const res: any[] = await firstValueFrom(this.authService.getCollection2('users'))
    console.log(res);
  }

Be cautious with firstValueFrom(), as it's advised to opt for observables over promises due to potential pitfalls outlined in the source link below.

If the observable stream completes without emitting any values, the promise will reject with EmptyError or resolve with a default value if provided.

If an error occurs in the observable stream, the promise will reject accordingly.

Take care to ensure the observable emits at least one value or completes to prevent hanging up promises in memory. Consider implementing timeout, take, takeWhile, or takeUntil methods.

source: https://rxjs.dev/api/index/function/firstValueFrom


To add a five-second timeout and error handling:

  async ngOnInit() {
    const res = await firstValueFrom(
      this.authService.getCollection2('users').pipe(timeout({ each: 5000 }))
    ).catch((err) => console.error(err));

    console.log(res);
  }

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

Having trouble retrieving my .scss bundle file from a different directory

I am trying to access my .scss file that imports all the other .scss files (they are all imported in styles.scss). Even though I have added the path to my angular cli file, the file is still not accessible and no styling is being applied to my html elemen ...

What exactly occurs when a "variable is declared but its value is never read" situation arises?

I encountered the same warning multiple times while implementing this particular pattern. function test() { let value: number = 0 // The warning occurs at this line: value is declared but its value is never read value = 2 return false } My curi ...

Issue: Unforeseen reserved term

I encountered an issue while running a simple test case where I received an error message stating "unexpected reserved keyword." The error is pointing to the import keyword in the line import * as chai from 'chai'. Below is my test code: "use st ...

Is there a way to safeguard against accidental modifications to MatTab without prior authorization?

I need to delay the changing of the MatTab until a confirmation is provided. I am using MatDialog for this confirmation. The problem is that the tab switches before the user clicks "Yes" on the confirmation dialog. For instance, when I try to switch from ...

Parameter types retain their generality when constrained by other types

In my function, I have a generic parameter T and another parameter that is restricted based on the passed T. For example: const example = { x: 'hello', y: 'world', } as const function test<T extends object, K extends keyof T> ...

What is the reason that the combination type of two interfaces that expand a generic interface cannot be used for that generic interface?

Within my codebase, I've established a straightforward generic interface W. Extending this interface are two more interfaces - T and N, each adding a property type that can be utilized in a tagged union scenario: interface W<V> { value: V } ...

Can you enforce function signatures on an existing library like rxjs using TypeScript?

My goal is to always include an error function when using rxjs's subscribe method. To achieve this, I am thinking of creating an interface by adding the following code snippet: Observable.prototype.sub = Observable.prototype.subscribe; By assigning ...

Imitate a worldwide entity with Jest and Typescript

I have a React component that utilizes the global object Routes provided by the Rails gem js-routes. My component is being tested with Jest's snapshot testing feature. The issue arises when trying to mock the Routes global object in the test to return ...

Guide to adding an optional parameter in the base href element within an Angular application

Hey there! I need to set an optional parameter so that either of the following URLs will work: http://myWebsiteName/myRouterPath or In this case, "mango" is the optional parameter. ...

`Designing a UI in Ionic version 2`

Is it possible to create a layout page in an Ionic v2 application so that the navbar and sidemenu can be displayed on every page without having to add them individually to each page? For example, ASP.NET uses master pages for websites to contain component ...

Expo background fetch initialized but not activated

During the development of my React Native app, I encountered the need to perform periodic background fetches from another server. To achieve this, I utilized two classes from Expo: import * as BackgroundFetch from 'expo-background-fetch'; import ...

The circular reference error in Typescript occurs when a class extends a value that is either undefined, not a constructor,

Let me begin by sharing some context: I am currently employed at a company where I have taken over the codebase. To better understand the issue, I decided to replicate it in a new nestjs project, which involves 4 entities (for demonstration purposes only). ...

Contrasting router and router-deprecated in angular2

After upgrading from version "beta.17" to "2.0.0-rc.1", I am a bit confused about when to use router and when to use router-deprecated. Can someone help clarify this for me? ...

What is the method for importing or requiring standard node modules in TypeScript?

How can I effectively use the require function in typescript with standard NPM modules? I am currently attempting to utilize the debug package. I have installed it from npm and also ran tsd install debug. However, I am facing issues where the syntax works ...

What is preventing MenuItemLink from being displayed in the menu?

I have created a unique page for users to purchase subscriptions, but I am having trouble accessing that page because the button is not appearing in the menu. I followed the steps outlined in the official guide, but only the dashboard and resources buttons ...

The error message "Highcharts - Incompatible types of 'series' property" is displayed

When trying to create charts in Angular using Highchart, I encountered an error during compilation. The properties seem to be compatible, but the TypeScript compiler is throwing an error that I don't quite understand. Any suggestions on how to avoid t ...

Combining types: unable to utilize the second optional type within a for loop

I am facing an issue while looping through an array due to the union type. I am wondering what I have overlooked in the code below that is causing Visual Studio Code to not recognize the second optional type for that specific array. class Menu { // name ...

The Aurelia application encounters a "Maximum call stack size exceeded" error while trying to bind FullCalendar

I am currently working on setting up a JQuery plugin (FullCalendar) within my Aurelia application, which is built using TypeScript. I am relatively new to web development and just trying to get a basic example up and running. To start off, I utilized this ...

What is the reason for the error message stating that "'VideoSource' is being used as a value here despite it only referring to a type?"

When I run the first if check, everything works fine without any errors. However, when I move to the else block, an error is thrown. The error message reads: 'VideoSource' only refers to a type, but is being used as a value here. let element ...

Issue with Nestjs and Jest: expect().toHaveReturnedWith() not returning any value

Currently experimenting with nestjs and jest in a personal project, I've encountered some challenges while trying to properly utilize the expect().toHaveReturnedWith() methods. I wonder if the issue lies in how I set the resolved value: jest.spyOn(su ...