Angular 2 - Routage - CanActivate fonctionne avec Observable

94

J'ai un AuthGuard (utilisé pour le routage) qui implémente CanActivate .

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

Mon problème est que le résultat CanActivate dépend d'un http-get-result - le LoginService renvoie un observable .

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

Comment puis-je les réunir - faire dépendre CanActivate d'un état backend?

# # # # # #

EDIT: Veuillez noter que cette question date de 2016 - un stade très précoce d'angular / router a été utilisé.

# # # # # #

Philipp
la source
2
Avez-vous lu ici? angular.io/docs/ts/latest/guide/router.html rechercher Route Guards Voici la référence API pour CanActivate: angular.io/docs/ts/latest/api/router/index/… comme vous le voyez, il peut retourner soit booléen ou observable <boolean>
mollwe
4
canActivate()peut renvoyer un Observable, assurez-vous simplement que le Observablea terminé (ie. observer.complete()).
Philip Bulley
1
@PhilipBulley et si l'observable émet plus de valeurs puis se termine? Que fait le gardien? Ce que j'ai vu jusqu'à présent, c'est l'utilisation de l' take(1)opérateur Rx pour atteindre la totalité du flux, que faire si j'oublie de l'ajouter?
Felix

Réponses:

146

Vous devez mettre à jour "@ angular / router" vers la dernière version. par exemple "3.0.0-alpha.8"

modifier 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);
        });
    }   
}

Si vous avez des questions, demandez moi!

Kery Hu
la source
3
Il convient de souligner que cela fonctionne avec les promesses d'une manière très similaire. Pour ma mise en œuvre, en supposant que isLoggedIn()c'est un Promise, vous pouvez le faire isLoggedIn().then((e) => { if (e) { return true; } }).catch(() => { return false; });J'espère que cela aidera les futurs voyageurs!
kbpontius
6
Je devais ajouterimport 'rxjs/add/observable/of';
marc_aragones
1
pas une bonne réponse maintenant IMO .. il ne fournit aucun détail de ce qui se passe côté serveur ... il est dépassé chez alpha! ... il ne suit pas cette meilleure pratique .. angular.io/docs/ts/latest/guide/… .. voir ma réponse mise à jour ci-dessous (j'espère un jour ci-dessus)
danday74
1
canActivate devrait relancer Observable <boolean>
Yoav Schniederman
Great Help :) Merci
gschambial
57

Mise à jour de la réponse de Kery Hu pour Angular 5 et RxJS 5.5 où l' catchopérateur est déconseillé. Vous devez maintenant utiliser l'opérateur catchError conjointement avec les opérateurs pipe et lettable .

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);
      })
    );
  }   

}
Derek Hill
la source
J'ai 1 autre appel XHR après isLoggedIn (), et le résultat de XHR est utilisé dans le 2ème appel XHR. Comment avoir un 2ème appel ajax qui acceptera le 1er résultat? L'exemple que vous avez donné est assez simple, pouvez-vous me dire comment utiliser pipe () si j'ai un autre ajax aussi.
Pratik
9

canActivate () accepte Observable<boolean>comme valeur renvoyée. Le garde attendra la résolution de l'observable et examinera la valeur. Si «vrai», il passera la vérification, sinon (toute autre donnée ou erreur générée) rejettera l'itinéraire.

Vous pouvez utiliser l' .mapopérateur pour transformer le Observable<Response>en Observable<boolean>comme ceci:

canActivate(){
    return this.http.login().map((res: Response)=>{
       if ( res.status === 200 ) return true;
       return false;
    });
}
mp3por
la source
1
qu'en est-il d'un bloc de capture? le bloc catch est appelé si c'est un droit 401?
danday74
1
En angulaire 4 ne fonctionne pas. Il a besoin d'un endroit pour définir un type générique.
gtzinos
2

Je l'ai fait de cette façon:

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

Comme vous pouvez le voir, j'envoie une fonction de secours à userService.auth que faire si l'appel http échoue.

Et dans userService j'ai:

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);
  });}
Karolis Grinkevičius
la source
vraiment bonne réponse en termes de fonction .map () - vraiment vraiment bien :) - n'a pas utilisé le rappel de secours - au lieu de cela je me suis abonné à l'auth Observable dans la méthode canActivate - merci beaucoup pour l'idée
danday74
0

Cela peut vous aider

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
Kalanka
la source
-1

CanActivate fonctionne avec Observable mais échoue lorsque 2 appels sont effectués comme CanActivate: [Guard1, Guard2].
Ici, si vous retournez un Observable de false de Guard1, il vérifiera également Guard2 et autorisera l'accès à la route si Guard2 renvoie true. Pour éviter cela, Guard1 doit retourner un booléen au lieu de Observable ou boolean.

Arjunsingh
la source
-10

dans canActivate (), vous pouvez retourner une propriété booléenne locale (par défaut à false dans votre cas).

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

Et puis dans le résultat du LoginService, vous pouvez modifier la valeur de cette propriété.

//...
this.loginService.login().subscribe(success => this._canActivate = true);
Nguyễn Việt Trung
la source
vous êtes un génie, monsieur.
Kien Nguyen Ngoc