2

I'm building an authentication system with email and password in firebase the login and logout both work fine as response console.log(authState) but the angular guard always returns the method isLoggedIn() is null I don't know why is that?! In the constructor I have defined it with userDetails value:

Code for just the auth.service constructor:

  public user: Observable<firebase.User>;
  public userDetails: firebase.User = null;


  constructor(
    private af: AngularFireAuth,
    private navCtrl: NavController,
    private statusMessage: ToastMessagesService
  ) {


    this.user = af.authState;

    this.user.subscribe(
      user => {
        this.userDetails = user;
        console.log(this.userDetails); // I get a response from this on the login page with user details ( user is logged in )
      }
    )

  }

Code of the method alone:

  isLoggedIn(): boolean {
    return (this.userDetails != null) ? true : false;
  }

The code of the whole service ( auth.service.ts ):

import { Injectable } from '@angular/core';
import { AngularFireAuth } from 'angularfire2/auth';
import { Observable } from 'rxjs/internal/observable';
import { NavController } from '@ionic/angular';
import { ToastMessagesService } from './toast-messages.service';
import * as firebase from 'firebase';

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

  public user: Observable<firebase.User>;
  public userDetails: firebase.User = null;


  constructor(
    private af: AngularFireAuth,
    private navCtrl: NavController,
    private statusMessage: ToastMessagesService
  ) {


    this.user = af.authState;

    this.user.subscribe(
      user => {
        this.userDetails = user;
        console.log(this.userDetails); 
      }
    )

  }

  async siginInRegular(username: string, password: string) {
    // const credentials = this.af.auth.email
    const results = await this.af.auth.signInWithEmailAndPassword(username, password).then(
      results => {
        this.navCtrl.navigateForward('/home');
        this.statusMessage.message(`Welcome ${results.user.email}`);
      }
    );

  }

  async register(username: string, password: string) {
    try {
      return await this.af.auth.createUserWithEmailAndPassword(username, password).then(
        user => {
          this.navCtrl.navigateForward('/profile');
          this.statusMessage.message(`Welcome ${user.user.email}`);
        }
      );
    } catch (error) {
      console.dir(error);
    }
  }

  signOut() {
    return this.af.auth.signOut();
  }

  isLoggedIn(): boolean {
    return (this.userDetails != null) ? true : false;
  }



}

The code for the guard:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {


  constructor(
    private auth: AuthService
  ) {
    console.log(auth.isLoggedIn());

  }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {


    if (this.auth.isLoggedIn()) {
      return true
    }

    console.log('Access denied!');
    return false;
  }
}
5
  • is there any reason to set to null at this line public userDetails: firebase.User = null; use empty object instead.
    – Ganesh
    Commented Feb 25, 2019 at 8:56
  • isLoggedIn method returning null is impossible. Commented Feb 25, 2019 at 8:58
  • 1
    You should check this post angularfirebase.com/snippets/…
    – Gonzalo
    Commented Feb 25, 2019 at 9:15
  • @ritaj The isLoggedIn() always return false because the userDetails always is null witch weird to me as well Commented Feb 25, 2019 at 9:15
  • @ganesh045 it doesn't matter will return an empty object if I do so! Commented Feb 25, 2019 at 9:25

1 Answer 1

2

There could be a race in your code. this.userDetails in your AuthService is not "ready" yet when you call it from your guard.

What you can do is, return a promise/observable in the canActivate method, as it accepts Observable<boolean> | Promise<boolean> | boolean.

a basic example could be this:

  1. Add a method to get the auth state in your auth service:

     get authState(): Observable<any> {
       return this.af.authState;
     }
    
  2. Use it in your canActivate() method:

    canActivate(): Observable<boolean> {
      return this.authService.authState()
        .pipe(
             map(authState => !!authState),
             tap(auth => !auth ? console.log('Access Denied!') : true),
             take(1)
         );
    }
    
5
  • Thanks a lot! your approach works really well :)! But I didn't understand still what cause this issue! because for example if I console.log(this.userDetails) in the constructor after the 'subscribe' if you look at the code above still gives me it's null!! but I'm sure it has a value because inside the subscribe when I log return a value but outside no! Commented Feb 25, 2019 at 9:21
  • I don't understand what are you referring to, try to be more clear so I can answer.
    – TheUnreal
    Commented Feb 25, 2019 at 9:26
  • Sorry about that! for simplicity, the userDetails property always returns null whatever where I 'console.log()' it! the only place it returns a value when it's inside the subscribe my question is why is this happen? Commented Feb 25, 2019 at 9:36
  • 1
    af.authState is an asynchronous method - the userDetails property will be null until the af.authState observable will be resolved from the server.
    – TheUnreal
    Commented Feb 25, 2019 at 9:39
  • Thanks for more explaining I have checked your answer :) have a nice day. Commented Feb 25, 2019 at 9:48

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.