Search code examples
angulartypescriptfirebase-authenticationrxjsgoogle-cloud-firestore

Angular 5, Firestore, OAuth - get auth uid in component


I have following issue

Got AuthService.ts where I authenticate my users with google account. In the same service I'm sending user details to Firestore db, such as uid, email, etc.

Now. In a component I need to get that uid from logged user, so in a constructor I've defined my AuthService and trying to get the uid.

When I go with this.auth.user.subscribe(user => console.log(user.uid)); It seems to work -> console logging the uid.

But, when I'm trying to assing it to a variable and console log / use the variable it goes with undefined error.

Here is the code:

this.auth.user.subscribe(user => this.userDoc = this.afs.doc(users/${user.uid})); this.user = this.userDoc.valueChanges(); console.log(this.user);

What am I doing wrong?

Thats my component code:

import { Component, OnInit } from '@angular/core';
import {AngularFirestore, AngularFirestoreDocument} from 'angularfire2/firestore';
import {Observable} from 'rxjs/Observable';
import {AuthService} from '../core/auth.service';

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


export class JudgeDashboardComponent implements OnInit {

  // entryDoc: AngularFirestoreDocument<any>;
  // entry: Observable<any>;
  userDoc: AngularFirestoreDocument<any>;
  user: Observable<any>;


  constructor(public auth: AuthService, private afs: AngularFirestore) { }

  ngOnInit() {
    this.auth.user.subscribe(user => console.log(user.uid)); //it works

    this.auth.user.subscribe(user => this.userDoc = this.afs.doc(`users/${user.uid}`));
    this.user = this.userDoc.valueChanges();
    console.log(this.user); // this one undefined
  }
}

And the AuthService:

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import * as firebase from 'firebase/app';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFirestore, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/switchMap';
import { User } from '../user';

@Injectable()
export class AuthService {

  user: Observable<User>;

  constructor(private afAuth: AngularFireAuth,
              private afs: AngularFirestore,
              private router: Router) {
    //// Get auth data, then get firestore user document || null
    this.user = this.afAuth.authState
      .switchMap(user => {
        if (user) {
          return this.afs.doc<User>(`users/${user.uid}`).valueChanges();
        } else {
          return Observable.of(null);
        }
      });
  }
  googleLogin() {
    const provider = new firebase.auth.GoogleAuthProvider();
    return this.oAuthLogin(provider);
  }
  private oAuthLogin(provider) {
    return this.afAuth.auth.signInWithPopup(provider)
      .then((credential) => {
        this.updateUserData(credential.user);
      });
  }
  private updateUserData(user) {
    // Sets user data to firestore on login
    const userRef: AngularFirestoreDocument<any> = this.afs.doc(`users/${user.uid}`);

    const data: User = {
      uid: user.uid,
      email: user.email,
      roles: {
        author: true
      },
      displayName: user.displayName || 'someDefaultValue',
      photoURL: user.photoURL
    };

    return userRef.set(data, { merge: true }); // without {merge:true} it will overwrite nor update.
  }
  signOut() {
    this.afAuth.auth.signOut().then(() => {
      this.router.navigate(['/']);
    });
  }
  ///// Abilities and Roles Authorizations
  ///// Assign roles to an ability method
  canRead(user: User): boolean {
    const allowed = ['admin', 'author', 'judge', 'partner'];
    return this.checkAutorization(user, allowed);
  }


  // determines if user has matching role
  private checkAutorization(user: User, allowedRoles: string[]) {
    if (!user) { return false; }
    for (const role of allowedRoles) {
      if ( user.roles[role] ) {
        return true;
      }
    }
    return false;
  }
}

Even if I go like this:

uid: string;
  ngOnInit() {
    this.auth.user.subscribe(user => {
      this.uid = user.uid;
      console.log(user.uid);
    });
    console.log(this.uid);

First console log works. Second one gives "undefined"


Solution

  • That's because it's an asynchronous method, considering this code block:

    ngOnInit() {
        this.auth.user.subscribe(user => {
          this.uid = user.uid;
          console.log(user.uid);
        });
        console.log(this.uid);
    }
    

    At first, subscribe to the user observable is called, then the second console.log(this.uid); (where this.uid is still undefined) is called, then when the value is pushed into the observable leading to the block inside the suscription callback being called:

    this.uid = user.uid;
    console.log(user.uid);
    

    It's all about being async!