Ryan Langton
Ryan Langton

Reputation: 6160

Angular2 CanActivate guard not working

I'm using the Observable<boolean> return for canActivate(). The following function was set up for testing, and it resolves correctly, the component displays.

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
  return Observable.from([{ admin: true }]).map(x =>
  {
    if (x.admin) return true;
    return false;
  });
}

However, the behavior of the actual code is that I stay on the login component despite the console output indicating the route should activate. The only real difference to the test above is I'm calling a service this.auth.isAdmin() instead of using Observable.from. The result of this.auth.isAdmin() is an Observable<boolean> with a value of true.

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
  const isAdmin$ = this.auth.isAdmin();
  return isAdmin$.map(x =>
  {
    console.log('isAdmin returned ' + x);
    if (!x) {
      console.log('redirectToLogin');
      this.auth.redirectToLogin(state.url);
      return false;
    } else {
      console.log('canActivate = true');
      return true;
    }
  });  
}

Here is the routing:

{
  path: 'admin',
  canActivate: [AdminGuard],
  children: [
    ...adminRoutes
  ]
},

Here is my console output:

isAdmin returned false
admin-guard.service.ts:27redirectToLogin
auth.service.ts:36 navigating to stored path "/admin"
auth.service.ts:21 Object {isAdmin: true, isPaid: false, $key: "xYFs8kMDpKdYKxDw4AL21FtnSWn1"}
admin-guard.service.ts:25 isAdmin returned true
admin-guard.service.ts:31 canActivate = true

And here is the isAdmin() function in case that is of interest:

isAdmin(): Observable<boolean> {
    if (!this.auth) return Observable.from([false]);
    const uid = this.auth.uid;
    return this.af.database.object(`user/${uid}`).do(x => console.log(x)).map(x => x.isAdmin);
}

Upvotes: 4

Views: 4447

Answers (1)

cartant
cartant

Reputation: 58400

The observable returned by your isAdmin function does not complete. AngularFire2 FirebaseObjectObservable instances do not complete; they emit objects as the underlying data changes.

Observables returned by guards have to complete. You can ensure this by using first (or take(1)) to complete the observable when the first value is emitted:

canActivate(
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot
): Observable<boolean> {
  const isAdmin$ = this.auth.isAdmin();
  return isAdmin$.first().map(x =>
  {
    console.log('isAdmin returned ' + x);
    if (!x) {
      console.log('redirectToLogin');
      this.auth.redirectToLogin(state.url);
      return false;
    } else {
      console.log('canActivate = true');
      return true;
    }
  });  
}

At the time of writing, it was necessary for the returned observable to complete. However, Angular now calls first on the returned observable, so there is no longer a requirement for the observable to complete.

Upvotes: 7

Related Questions