Search code examples
angulargoogle-cloud-firestoreangular2-observables

Angular Behaviour Subject with Firestore


Exceptionally new to BehaviourSubjects and not entirely sure if I've approached this the best way.

I've created a service that queries Firestore. I've tried to create a BehaviourSubject based on the returned data so that I can use it in multiple components. What's the best way to make sure each component always get's the latest updated version?

There are parts of my application where I'll update firestore with new values. When that happens, will it automatically sync through the service and into my components?

Any examples of best practise or how to get this working in a good way would be hugely appreciated. Thanks!

Service

import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';
import { AngularFirestore, AngularFirestoreDocument } from '@angular/fire/firestore';

@Injectable({
  providedIn: 'root'
})
export class SettingsService {

  private settingsDocRef: AngularFirestoreDocument<any>;


  settingsDocument = new BehaviorSubject(this.settingsDocRef);
  settings$: Observable<any> = this.settingsDocument.asObservable();

  constructor(private readonly afs: AngularFirestore) {
    this.settingsDocRef = this.afs.doc(`user_settings/useridishere`);
    this.settings$ = this.settingsDocRef.snapshotChanges();
    this.settings$.subscribe((value) => {
      const data = value.payload.data();
      this.settingsDocument.next(data);
    });
  }
}

Component.ts

import { Component, OnInit } from '@angular/core';
import { SettingsService } from '../settings.service';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css']
})

export class HelloComponent implements OnInit {

  issueName: string;

  // OR should this be an observable?
  // issueName: Observable<string>


  constructor(private settingsService: SettingsService ) {}

  ngOnInit() {
     this.settingsService.settings$.subscribe((settings) => {
      this.issueName = settings.usersetting_issuename;
    })
  }

}

HTML

<div>
  {{ issueName }}
</div> 

<!-- OR if it's supposed to be an observable? -->
<div>
  {{ issueName | async }}
</div> 

Solution

  • You don't need to use a subject in your service to keep track of your data. Only the current loaded component needs to subscribe to your data.

    Service

    Your service will be responsible to define and return observables linked to your firestore database.

    import { Injectable } from '@angular/core';
    import { Observable} from 'rxjs';
    import { AngularFirestore, AngularFirestoreDocument } from '@angular/fire/firestore';
    
    @Injectable({
      providedIn: 'root'
    })
    export class SettingsService {
    
    
      constructor(private readonly afs: AngularFirestore) {}
    
      getUserSettings(userID: string): Observable<any> {
          const settingsDocRef = this.afs.doc(`user_settings/${userID}`);
          return settingsDocRef.valueChanges();
      }
    }
    

    I have used valueChanges() instead of snapshotChanges() since you only need your document data.

    Component.ts

        import { Component, OnInit } from '@angular/core';
        import { SettingsService } from '../settings.service';
        import { Observable } from 'rxjs';
    
        @Component({
          selector: 'app-hello',
          templateUrl: './hello.component.html',
          styleUrls: ['./hello.component.css']
        })
    
        export class HelloComponent implements OnInit {
    
          issueName$: Observable<string>;
    
          constructor(private settingsService: SettingsService ) {}
    
          ngOnInit() {
             this.issueName$ = this.settingsService.getUserSettings('yourUserID').pipe(
                 map((settings) => settings.usersetting_issuename)
             );
          }
    
        }
    

    I didn't add the implementation to get your user ID. You will need to figure it out yourself or ask another question.

    HTML

    as you have suggested it yourself, you subscribe to your observable in the template.

    <!-- OR if it's supposed to be an observable? -->
    <div>
      {{ issueName$ | async }}
    </div>