I have an Ionic4/Angular app connected to a Firebase store. I have Firebase authentication working and can retrieve Firebase data to populate an array. But when it comes to looking up a value from one Firebase table and then using a field from the returned document to look another table, I'm hitting brick walls. What I'm trying to do is lookup a User record from the auth.currentuser.uId and return the document so I can extract other field values.

I have created a service to retrieve Users (a Firebase table separate to the builtin authentication to provide profile information), and another service for retrieving Drama (another Firebase table) records. Within the ngInit I am successfully retrieving Drama records via that service and displaying them on the page, although that is just returning all records. My call to return a User via query is not working.

Here is my Drama service

import { Injectable } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Drama {
  id?: string;
  dramaName: string;
  image: string;
  groupId: string;
}

@Injectable({
  providedIn: 'root'
})
export class DramaService {
  private dramasCollection: AngularFirestoreCollection<Drama>;

  private dramas: Observable<Drama[]>;

  constructor(db: AngularFirestore) {
    this.dramasCollection = db.collection<Drama>('Drama');

    this.dramas = this.dramasCollection.snapshotChanges().pipe(
      map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data();
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      })
    );
  } 

  getDramas() {
    return this.dramas;
  }

  getDramasByGroup(groupId: string) {
    return this.dramas;
  }

  getDrama(id) {
    return this.dramasCollection.doc<Drama>(id).valueChanges();
  }

  updateDrama(drama: Drama, id: string) {
    return this.dramasCollection.doc(id).update(drama);
  }

  addDrama(drama: Drama) {
    return this.dramasCollection.add(drama);
  }

  removeDrama(id) {
    return this.dramasCollection.doc(id).delete();
  }
}

and this call to it works as expected in the page

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Drama, DramaService } from '../services/dramas.service'

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

export class DramaPage implements OnInit {

  dramas: Drama[];

  constructor(
    public globalService: GlobalService,
    private router: Router,
    private dramaService: DramaService) {}

  ngOnInit() {
    this.dramaService.getDramas().subscribe(res => {
    this.dramas = res;
    });
  }
}

here is the User service...

import { Injectable } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { defineBase } from '@angular/core/src/render3';
import * as firebase from 'firebase';

export interface User {
  id?: string;
  firstName: string;
  lastName: string;
  email: string;
  groupId: string;
  userId: string;
}

@Injectable({
  providedIn: 'root'
})

export class UserService {
  private usersCollection: AngularFirestoreCollection<User>;

  private users: Observable<User[]>;

  constructor(db: AngularFirestore) {
    this.usersCollection = db.collection<User>('Users');

    this.users = this.usersCollection.snapshotChanges().pipe(
      map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data();
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      })
    );
  }

  getUsers() {
    return this.users;
  }

  getUser(id) { 
    let db: AngularFirestore;
    this.usersCollection = db.collection<User>('Users', ref => ref.where('userId', '==', id));
    this.users = this.usersCollection.snapshotChanges().pipe(
      map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data();
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      })
    );
    return this.users;
  }

  updateUser(user: User, id: string) {
    return this.usersCollection.doc(id).update(user);
  }

  addUser(user: User) {
    return this.usersCollection.add(user);
  }

  removeUser(id) {
    return this.usersCollection.doc(id).delete();
  }
}

and here is the the revised page calling getUser(id) that doesn't work...

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Drama, DramaService } from '../services/dramas.service';
import { User, UserService } from '../services/users.service';
import { AngularFireAuth } from '@angular/fire/auth';

@Component({
  selector: 'app-drama',
  templateUrl: './drama.page.html',
  styleUrls: ['./drama.page.scss'],
  providers: [GlobalService]
})

export class DramaPage implements OnInit {

  dramas: Drama[];
  user: User[];
  uid: string;

  constructor(
    public globalService: GlobalService,
    private router: Router,
    private dramaService: DramaService,
    private userService: UserService,
    public afAuth: AngularFireAuth) {}

  ngOnInit() {
//this call doesn't work
    this.afAuth.authState.subscribe(authUser => {
      if (authUser){
        console.log('drama page authUser: ', authUser.uid);
        this.userService.getUser(authUser.uid).subscribe(res => {
          this.user=res;
          console.log(this.user);
        });
      }
    })

    this.dramaService.getDramas().subscribe(res => {
    this.dramas = res;
    });
  }
}

In the console I'm getting: "core.js:15724 ERROR TypeError: Cannot read property 'collection' of undefined", which makes me think that I'm not declaring or initialising my AngularFireStore instance properly. But this is the approach used in the service constructor that does work.

getUser(id) { 
    let db: AngularFirestore;
    this.usersCollection = db.collection<User>('Users', ref => ref.where('userId', '==', id)); //undefined error here
    this.users = this.usersCollection.snapshotChanges().pipe(
      map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data();
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      })
    );
    return this.users;
  }

1 Answers

1
Shashank Vivek On Best Solutions

Its happening because you overlooked DependencyInjection(DI) vs variable declaration.

In DramaService , you have injected(DI) instance of AngularFirestore in constructor as:

  constructor(db: AngularFirestore) {
    this.dramasCollection = db.collection<Drama>('Drama');

    this.dramas = this.dramasCollection.snapshotChanges().pipe(
      map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data();
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      })
    );
  } 

where as in UserService , you have declared db varible of type AngularFirestore but never assigned any value, so its an undefined varaible.

  getUser(id) { 
    let db: AngularFirestore; // <-- declaration but no initialization.
    this.usersCollection = db.collection<User>('Users', ref => ref.where('userId', '==', id));
    this.users = this.usersCollection.snapshotChanges().pipe(
      map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data();
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      })
    );
    return this.users;
  }

Try in UserService:

  constructor(private db: AngularFirestore) {
    this.usersCollection = this.db.collection<User>('Users');

    this.users = this.usersCollection.snapshotChanges().pipe(
      map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data();
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      })
    );
  }

in getUser(id)


  getUser(id) { 
    this.usersCollection = this.db.collection<User>('Users', ref => ref.where('userId', '==', id));
    this.users = this.usersCollection.snapshotChanges().pipe(
      map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data();
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      })
    );
    return this.users;
  }