Search code examples
angulargoogle-cloud-firestoreangularfire2

Referencing a field on document in Angular Firestore


I'm trying to reference one of the fields of a document in the component.ts file. The code below works, as long as I explicitly declare let day = "Sunday".

As there is a field "day" in this.meter, I would like to be able to declare let day = this.meter.day instead. I know that I have to somehow subscribe to this.meter, but I'm lost as to how.

I've gone through all the documentation, and I'm completely overwhelmed and very confused, as this should be a relatively simple operation.

Thank you.

import { Component, OnInit, ViewChild, ElementRef, HostListener, AfterViewInit } from '@angular/core';
import { AngularFirestore, AngularFirestoreDocument, AngularFirestoreCollection, AngularFirestoreCollectionGroup } from '@angular/fire/firestore';
import { Observable, Subscriber } from 'rxjs';
import { ActivatedRoute } from '@angular/router';

export interface Meter { meter: string; units: number; lastupdated: string; year: string; month: string; day: string; monthday: string}

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

export class MeterComponent implements OnInit {
  meterId: string;
  private meterDoc: AngularFirestoreDocument<Meter>;
  meter: Observable<Meter>;
  dailydata: Observable<any>;
  todayinweek: Observable<any>;
  pastweek: Observable<any>;
  day: string;

  constructor(
    private route: ActivatedRoute, private db: AngularFirestore, private afs:AngularFirestore) {
    this.meterId = this.route.snapshot.paramMap.get('id');
    this.meter = this.afs.doc<Meter>(`meters/${this.meterId}`).valueChanges();
    }

ngOnInit() {


**//let day = this.meter.day;**
let day = "Sunday";


 this.dailydata = this.db.collection('meters').doc<any>(this.meterId).collection('dailydata').doc(day).valueChanges();
 this.todayinweek = this.db.collection('meters').doc<any>(this.meterId).collection(('meterdata'), ref=> ref.where("day", "==", day).limit(10)).valueChanges();
 this.pastweek = this.db.collection('meters').doc<any>(this.meterId).collection(('meterdata'), ref=> ref.orderBy("date", "desc").limit(7)).valueChanges();

 }

}

I've tried following Get field from Firestore document, but it didn't work for me. I received an error that "Property 'then' does not exist on type 'Observable'"


Solution

  • You can make every variable an observable, and just let the data stream through your component :)

    First you get the current id from the route param observable and with this you get the meter angularfire collection with the id.

    Then from this id combined with the meter observable, you can get all the other values.

    readonly meterDoc = this.route.paramMap.pipe(
      map((params) => params.get('id')),
      distinctUntilChanged(),
      map((id) => this.db.collection('meters').doc<Meter>(id)),
      shareReplay({ refCount: true, bufferSize: 1 })
    );
    
    readonly meter = this.meterDoc.pipe(
      map((meter) => meter.valueChanges())
    );
    
    readonly docMeter = combineLatest([
      this.meterDoc,
      this.meter
    ]);
    
    readonly dailydata = this.docMeter.pipe(
      switchMap(([ doc, meter ]) => doc.collection('dailydata').doc(meter.day).valueChanges())
    );
    
    readonly todayinweek = this.docMeter.pipe(
      switchMap(([ doc, meter ]) => doc.collection(
        'meterdata',
        ref => ref.where("day", "==", meter.day).limit(10)
      ).valueChanges())
    );
    
    readonly pastweek = this.meterDoc.pipe(
      switchMap((doc) => doc.collection(
        'meterdata',
        ref => ref.orderBy("date", "desc").limit(7)
      ).valueChanges())
    );
    

    untested code though


    As you are reluctant to move everything to streams, even though, all the data you have is async. But luckily for you there are other options. You can use a route resolver to get the meter, and read this in your component:

    @Injectable({ providedIn: 'root' })
    export class MeterResolve implements Resolve<Meter> {
      resolve(route: ActivatedRouteSnapshot): Observable<Meter> {
        return this.db.collection('meters')
          .doc<Meter>(route.params.id)
          .valueChanges()
          .pipe(
            take(1)
          );
      }
    }
    

    Add this to your id route:

    { route: 'meter/:id', component: MeterComponent, resolve: { meter: MeterResolve } } 
    

    You can then read this in your component:

    readonly meterId = this.route.snapshot.params.id;
    readonly meter = this.route.snapshot.data.meter;
    readonly meterDoc = this.db.collection('meters').doc<Meter>(this.meterId);
    readonly dailydata = this.meterDoc.collection('dailydata').doc(this.meter.day).valueChanges();
    readonly todayinweek = this.meterDoc.collection(
        'meterdata',
        ref => ref.where("day", "==", this.meter.day).limit(10)
      ).valueChanges();
    readonly pastweek = this.meterDoc.collection(
        'meterdata',
        ref => ref.orderBy("date", "desc").limit(7)
      ).valueChanges();
    

    If for some reason, you don't even want to use resolvers, you can also do a subscribe in your ngOnInit, and go from there.. but.. please don't :) (this is based on the code in your question)

    ngOnInit() {
      this.meter.pipe(
        take(1)
      ).subscribe(({ day }) => {
        this.dailydata = this.db.collection('meters').doc<any>(this.meterId).collection('dailydata').doc(day).valueChanges();
        this.todayinweek = this.db.collection('meters').doc<any>(this.meterId).collection(('meterdata'), ref=> ref.where("day", "==", day).limit(10)).valueChanges();
        this.pastweek = this.db.collection('meters').doc<any>(this.meterId).collection(('meterdata'), ref=> ref.orderBy("date", "desc").limit(7)).valueChanges();  
      });
    }