Angular 2 - Routing - CanActivate work with Observable

AngularAngular2 RoutingAngular2 Http

Angular Problem Overview


I have an AuthGuard (used for routing) that implements CanActivate.

canActivate() {
    return this.loginService.isLoggedIn();
}

My problem is, that the CanActivate-result depends on a http-get-result - the LoginService returns an Observable.

isLoggedIn():Observable<boolean> {
    return this.http.get(ApiResources.LOGON).map(response => response.ok);
}

How can i bring those together - make CanActivate depend on a backend state?

# # # # #

EDIT: Please note, that this question is from 2016 - a very early stage of angular/router has been used.

# # # # #

Angular Solutions


Solution 1 - Angular

You should upgrade "@angular/router" to the latest . e.g."3.0.0-alpha.8"

modify AuthGuard.ts

@Injectable()
export class AuthGuard implements CanActivate {
    constructor(private loginService: LoginService, private router: Router) {}

    canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        return this.loginService
            .isLoggedIn()
            .map((e) => {
                if (e) {
                    return true;
                }
            })
            .catch(() => {
                this.router.navigate(['/login']);
                return Observable.of(false);
            });
    }
}

If you have any questions, ask me!

Solution 2 - Angular

Updating Kery Hu's answer for Angular 5+ and RxJS 5.5 where the catch operator is deprecated. You should now use the catchError operator in conjunction with pipe and lettable operators.

import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { catchError, map} from 'rxjs/operators';
import { of } from 'rxjs/observable/of';

@Injectable()
export class AuthGuard implements CanActivate {

  constructor(private loginService: LoginService, private router: Router) { }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>  {
    return this.loginService.isLoggedIn().pipe(
      map(e => {
        if (e) {
          return true;
        } else {
          ...
        }
      }),
      catchError((err) => {
        this.router.navigate(['/login']);
        return of(false);
      })
    );
  }   
  
}

Solution 3 - Angular

canActivate() accepts Observable<boolean> as returned value. The guard will wait for the Observable to resolve and look at the value. If 'true' it will pass the check, else ( any other data or thrown error ) will reject the route.

You can use the .map operator to transform the Observable<Response> to Observable<boolean> like so:

canActivate(){
    return this.http.login().map((res: Response)=>{
       if ( res.status === 200 ) return true;
       return false;
    });
}

Solution 4 - Angular

I've done it in this way:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.userService.auth(() => this.router.navigate(['/user/sign-in']));}

As you can see I'm sending a fallback function to userService.auth what to do if http call fails.

And in userService I have:

import 'rxjs/add/observable/of';

auth(fallback): Observable<boolean> {
return this.http.get(environment.API_URL + '/user/profile', { withCredentials: true })
  .map(() => true).catch(() => {
    fallback();
    return Observable.of(false);
  });}

Solution 5 - Angular

This may help you

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { Select } from '@ngxs/store';
import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { AuthState } from 'src/app/shared/state';

export const ROLE_SUPER = 'ROLE_SUPER';

@Injectable()
export class AdminGuard implements CanActivate {

 @Select(AuthState.userRole)
  private userRoles$: Observable<string[]>;

  constructor(private router: Router) {}

 /**
   * @description Checks the user role and navigate based on it
   */

 canActivate(): Observable<boolean> {
    return this.userRoles$.pipe(
      take(1),
      map(userRole => {
        console.log(userRole);
        if (!userRole) {
          return false;
        }
        if (userRole.indexOf(ROLE_SUPER) > -1) {
          return true;
        } else {
          this.router.navigate(['/login']);
        }
        return false;
      })
    );
  } // canActivate()
} // class

Solution 6 - Angular

CanActivate does work with Observable but fails when 2 calls are made like CanActivate:[Guard1, Guard2].
Here if you return an Observable of false from Guard1 then too it will check in Guard2 and allow access to route if Guard2 returns true. In order to avoid that, Guard1 should return a boolean instead of Observable of boolean.

Solution 7 - Angular

in canActivate(), you can return a local boolean property (default to false in your case).

private _canActivate: boolean = false;
canActivate() {
  return this._canActivate;
}

And then in the result of the LoginService, you can modify the value of that property.

//...
this.loginService.login().subscribe(success => this._canActivate = true);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPhilippView Question on Stackoverflow
Solution 1 - AngularKery HuView Answer on Stackoverflow
Solution 2 - AngularDerek HillView Answer on Stackoverflow
Solution 3 - Angularmp3porView Answer on Stackoverflow
Solution 4 - AngularKarolis GrinkevičiusView Answer on Stackoverflow
Solution 5 - AngularKalankaView Answer on Stackoverflow
Solution 6 - AngularArjunsinghView Answer on Stackoverflow
Solution 7 - AngularNguyễn Việt TrungView Answer on Stackoverflow