Home > Software design >  How to observe a Firestore document with AngularFire? `onSnapshot() not working?
How to observe a Firestore document with AngularFire? `onSnapshot() not working?

Time:09-16

Using Angular 14 and AngularFire 7.4.1, I made an observer for a Firestore collection. It works great:

interface Scientist {
  name?: string | null,
  born?: number | null,
  accomplishment?: string | null
};

scientist$: Observable<Scientist[]>;

constructor(public firestore: Firestore) {
    this.scientist$ = collectionData(collection(firestore, 'scientists'));
}

The collection displays in the HTML view.

I see in the Firestore documentation that it's also possible to observe a single document. I'm a big fan of Charles Babbage so I want to observe this document:

interface Scientist {
  name?: string | null,
  born?: number | null,
  accomplishment?: string | null
};

scientist$: Observable<Scientist[]>;
charle$: Observable<Scientist>;

constructor(public firestore: Firestore) {
  this.scientist$ = collectionData(collection(firestore, 'scientists')); // works
  this.charle$ = onSnapshot(doc(firestore, 'scientists', 'Charles Babbage')); // doesn't work
}

That throws an error:

Type 'Unsubscribe' is not assignable to type 'Observable<Scientist>'.

Oh yeah, Firestore returns my data wrapped in a document. Let's change the data type to any:

charle$: Observable<any>;

That throws the same error. There seems to be something incompatible between Angular's Observable and Firestore's onSnapshot. Is there a AngularFire single document observer, e.g., documentData(doc(firestore, 'scientists', 'Charles Babbage')?

I have a second, related question. How do I detach the collection listener?

CodePudding user response:

The onSnapshot() returns a function that can been called to detach the listener. It also takes a function as second param that'll trigger every time an update is received. Try refactoring the code as shown below:

const unsub = onSnapshot(doc(firestore, 'scientists', 'Charles Babbage'), (snapshot) => {
  console.log("> Updated received", doc.data())
  // TODO: Update data state
});

You can then update the state from the function itself. Just call the unsub(); and the listener will be detached.

CodePudding user response:

Here is the code based on Dharmaraj's answer.

import { Firestore, doc, collectionData, collection, onSnapshot } from '@angular/fire/firestore';

interface Scientist {
  name?: string | null,
  born?: number | null,
  accomplishment?: string | null
};

export class AppComponent {
  scientist$: Observable<Scientist[]>;
  charle$: Scientist  = {
    name: null,
    born: null,
    accomplishment: null
  };
  unsubCharle$: any;

  constructor(public firestore: Firestore) {
    this.scientist$ = collectionData(collection(firestore, 'scientists')); // collection listener
    this.unsubCharle$ = onSnapshot(doc(firestore, 'scientists', 'Charles Babbage'), (snapshot: any) => { // document listener
        this.charle$.name = snapshot.data().name;
        this.charle$.born = snapshot.data().born;
        this.charle$.accomplishment = snapshot.data().accomplishment;
    });
  }

  async detachListener() {
    console.log("Detaching listener.");
    this.unsubCharle$();
  }
}
<h3>Observe (collection)</h3>
<ul>
    <li *ngFor="let scientist of scientist$ | async">
        {{scientist.name}}, born {{scientist.born}}: {{scientist.accomplishment}}
    </li>
</ul>

<h3>Observe (single document, 'Charles Babbage')</h3>

<div *ngIf="charle$.name">{{ charle$.name }}, born {{ charle$.born }}: {{ charle$.accomplishment}}</div>

<form (ngSubmit)="detachListener()">
    <button type="submit" value="detachListener">Detach Listener</button>
</form>

This makes two observers, a collection listener and a document listener. The document listener can be detached.

  • Related