skip to Main Content

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?

2

Answers


  1. Chosen as BEST ANSWER

    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.


  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search