Angular2 canActivate() calling async function

Evan Salter picture Evan Salter · Jul 17, 2016 · Viewed 55.4k times · Source

I am trying to use Angular2 router guards to restrict access to some pages in my app. I am using Firebase Authentication. In order to check if a user is logged in with Firebase, I have to call .subscribe() on the FirebaseAuth object with a callback. This is the code for the guard:

import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AngularFireAuth } from "angularfire2/angularfire2";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";

@Injectable()
export class AuthGuard implements CanActivate {

    constructor(private auth: AngularFireAuth, private router: Router) {}

    canActivate(route:ActivatedRouteSnapshot, state:RouterStateSnapshot):Observable<boolean>|boolean {
        this.auth.subscribe((auth) => {
            if (auth) {
                console.log('authenticated');
                return true;
            }
            console.log('not authenticated');
            this.router.navigateByUrl('/login');
            return false;
        });
    }
}

When a navigate to a page that has the guard on it, either authenticated, or not authenticated is printed to the console (after some delay waiting for the response from firebase). However, the navigation is never completed. Also, if I am not logged in I am redirected to the /login route. So, the issue I am having is return true doesn't display the requested page to the user. I'm assuming this is because I am using a callback, but I am unable to figure out how to do it otherwise. Any thoughts?

Answer

G&#252;nter Z&#246;chbauer picture Günter Zöchbauer · Jul 17, 2016

canActivate needs to return an Observable that completes:

@Injectable()
export class AuthGuard implements CanActivate {

    constructor(private auth: AngularFireAuth, private router: Router) {}

    canActivate(route:ActivatedRouteSnapshot, state:RouterStateSnapshot):Observable<boolean>|boolean {
        return this.auth.map((auth) => {
            if (auth) {
                console.log('authenticated');
                return true;
            }
            console.log('not authenticated');
            this.router.navigateByUrl('/login');
            return false;
        }).first(); // this might not be necessary - ensure `first` is imported if you use it
    }
}

There is a return missing and I use map() instead of subscribe() because subscribe() returns a Subscription not an Observable