Error message stating: rxjs and firebase encountered a TypeError when attempting to add property 0 because the object is not

My angular application interacts with firebase firestore as the backend database.

I am working on a function to retrieve document snapshots from firestore in a generic way.

Here is the code snippet where I encounter an error:

  /**
   * Get a 'listener' observable that subscribes to live document updates in the firestore db
   * @type the expected document type as interface
   * @param docPath path to document collection in firestore
   * @param docId id of document to get
   * @returns An Obseravble<DocumentSnapShot<Type>>
   */
  public watchDocument<T>(
    docPath: string,
    docId: string
  ): Observable<DocumentSnapshot<T>> {
    const docRef = doc(this.firestore, docPath, docId) as DocumentReference<T>;
    return fromEventPattern<DocumentSnapshot<T>>(
      (handler) => onSnapshot<T>(docRef, handler),
      (handler, unsubscribe) => unsubscribe()
    );
  }

I have also tried another version of the function:

  public watchDocument<T>(
    docPath: string,
    docId: string
  ): Observable<DocumentSnapshot<T>> {
    const docRef = doc(this.firestore, docPath, docId) as DocumentReference<T>;
    return new Observable<DocumentSnapshot<T>>((subscriber) =>
      onSnapshot<T>(docRef, subscriber.next, subscriber.error)
    );
  }

Both versions result in the following error when calling onSnapshot(docRef, handler):

 TypeError: Cannot add property 0, object is not extensible
    at Array.push (<anonymous>)
    at ObserverProxy.subscribe...

This function is invoked from an NGRX effect. It works fine when called individually but fails when chained with other actions/effects.

EDIT: 1

I suspect that something is being frozen by NGRX. The firestore functions work seamlessly when used alone, but they fail after any firebase authentication functions like signInWithEmailAndPassword are executed.


QUESTION:

I need assistance in understanding why these errors occur when this function is combined with other actions.

Answer №1

It is recommended to return the unsubscription method from the onSnapshot in order to properly handle it within the Observable, as demonstrated in your code.

I have conducted tests with the following modifications:

watchDocument<T>(docPath: string,
    docId: string): Observable<DocumentSnapshot<T>> {
    const docRef = doc(this.firestore, docPath, docId) as DocumentReference<T>;
    return new Observable<DocumentSnapshot<T>>((subscriber) => {
      const subscription = onSnapshot<T>(docRef, snapshot => {
        subscriber.next(snapshot);
      });
      return () => {
        subscription();
      };
    });
  }

Retrieving document data using the above method:

this.service.watchDocument(this.collectionName, this.documentId)
    .subscribe(document => {
      console.log(document.id, " ==> ", document.data())
    });

By incorporating the same logic into your implementation of fromEventPattern, you can achieve:

watchDocumentWithEvent<T>(docPath: string,
    docId: string): Observable<DocumentSnapshot<T>> {
      const docRef = doc(this.firestore, docPath, docId) as DocumentReference<T>;
    return fromEventPattern<DocumentSnapshot<T>>(
      (handler) => {
        const subscription = onSnapshot<T>(docRef, handler);
        return () => subscription();
      },
      (subscription) => subscription()
    );
  }

The fromEventPattern function returns an Observable based on the type returned by the provided callback functions. In this case, the subscription serves as the unsubscribe method from onSnapshot, ensuring proper cancellation when unsubscribing from the Observable.

This information was gathered from sources such as RxJS documentation on fromEventPattern and references to onSnapshot from TypeScript VSCode IntelliSense and Firebase documentation.

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

Set up Angular 2 Universal on your system

Update: I attempted to set up Angular 2 Universal following this guide, but encountered errors when executing the command below. Here is the command I ran: typings install node express body-parser serve-static dexpress-serve-static-core mime --global -- ...

Choose between creating an observable pipe within a function or storing it in a variable

Currently, I have a functional code snippet that leverages the Angular service to create an Observable pipeline. This pipeline utilizes operators like mergeMap, filter, map, and shareReplay(1) to manage user authentication and fetch the onboarding status f ...

Navigating the complexities of defining conditions in routes within Angular/ionic applications may seem daunting,

Utilizing the ionic 5 framework for building an application, I am looking to implement a condition in the route to redirect users if they are already signed in. Within the app-routing.module.ts file: const routes: Routes = [ { path: '&apo ...

Angular form field not connected to data source

Here is a form I'm working with: <form #appForm> <div...> <select id="transversal" name="transversal" [ngModel]="app.transversal" type="select" required #transversal="ngModel"> < ...

Simulating @Input data for an Angular component using Jest

As we transition our Jasmine tests to Jest in our Angular project, we are encountering issues when attempting to mock @Input values of components in the tests. Previously, in Jasmine, we would write code like this: @Component({ selector: 'app-messag ...

Utilizing mat dialog in conjunction with the bootstrap sticky top class to enhance functionality

I am encountering an issue where, upon clicking to delete an entry, a mat dialog should appear with everything else in the background greyed out. However, the problem I am facing is that when I attempt to delete an entry, the dialog appears but the sticky ...

The response parser in Angular 7 is failing to function correctly

Hey, I recently updated my Angular from version 4.4 to the latest 7 and after encountering several errors, I was able to get my service up and running. However, I'm facing an issue with my output parser function which is supposed to parse the login re ...

Mixing pipe operators with Angular Observables in an array

In my project, I have an interesting scenario where I am using Observables and piping them to multiple functions as shown below. getOrders(filters: any, page: any = 1, orderBy: string = 'deliver_on', sort: string = 'asc') { const op ...

Perform a child component function in Angular

I'm working on a project with a child component as a map and a parent component as a form. The parent component has a field for writing the address, and when an address is entered, an HTTP request is triggered to find the latitude and longitude coordi ...

What is the best way to send an object to an Angular form?

I am facing an issue with my Spring entity, Agent, which includes an Agency object. When adding a new agent, I need to pass the agency as an object in the Angular form. While the backend code is functioning correctly, I am struggling to figure out how to p ...

The absence of a scroll bar on the web application is preventing me from scrolling properly

The vertical scroll bar on my Angular app seems to have disappeared mysteriously. I haven't made any changes to the code recently, and I'm struggling to figure out why it's missing. I've spent days trying to troubleshoot this issue with ...

Unlocking the potential: passing designated text values with Javascript

In my current React code, I am retrieving the value from cookies like this: initialTrafficSource: Cookies.get("initialTrafficSource") || null, Mapping for API const body = {Source: formValue.initialTrafficSource} Desired Output: utmcsr=(direct)|utmcmd=(n ...

Choosing options using an enum in Angular 2

In my TypeScript code, I have defined an enum called CountryCodeEnum which contains the values for France and Belgium. export enum CountryCodeEnum { France = 1, Belgium = 2 } Now, I need to create a dropdown menu in my form using this enum. Each ...

Sending Multiple Data from Multiple Rows in Angular

I am struggling to figure out how to submit an array of data when clicking on multiple rows in Angular. Specifically, I am confused about adding rows and submitting data from those newly added rows. I have been utilizing (ngSubmit) for the submission pro ...

Encountering the error message "Expected undefined to be truthy" while testing the creation of a Component

Recently, I've been tasked with enhancing my skill set by writing Jasmine/Karma tests for an Angular 9 application. After completing an online tutorial and doing some research via Google, I began working on my initial test cases independently. However ...

Utilize Hostbinding in Angular to Inject Style Declarations

Is there a way to efficiently inject multiple style declarations into a component using the @HostBinding decorator? I've been attempting the following: @HostBinding('style') get style(): CSSStyleDeclaration { return { background: &apo ...

Modifying the user interface (UI) through the storage of data in a class variable has proven to be

If I need to update my UI, I can directly pass the data like this: Using HTML Template <li *ngFor="let post of posts; let i = index;"> {{i+1}}) {{post.name}} <button (click)="editCategory(post)" class="btn btn-danger btn-sm">Edit</butto ...

Typescript allows you to apply a filter to an array

Query: Is there a way to display a pre-selected item from my dropdown using its unique ID? Issue/Explanation: The dropdown options in my web service are dynamically fetched based on a user's ZipCode. For example, a Provider is displayed as {Pho ...

Error encountered during conversion to Typescript for select event and default value

When trying to set the defaultValue in a Select component, TSlint throws an error - Type 'string' is not assignable to type 'ChangeEvent<HTMLInputElement> | undefined - for the code snippet below: const App = () => { const [ mont ...

Utilizing TypeScript with Express for dynamic imports

import * as dotenv from 'dotenv'; dotenv.config(); const env: string = process.env.NODE_ENV || 'development'; export const dynamicConfig = async () => { const data = await import('./config.' + env); console.log('d ...