Angular 2 - Routing - CanActivate funziona con Observable


94

Ho un AuthGuard (utilizzato per il routing) che implementa CanActivate .

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

Il mio problema è che il risultato di CanActivate dipende da un risultato http-get - LoginService restituisce un Observable .

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

Come posso riunirli - fare in modo che CanActivate dipenda da uno stato di backend?

# # # # # #

EDIT: Si prega di notare che questa domanda è del 2016: è stata utilizzata una fase molto iniziale di angolare / router.

# # # # # #


2
Hai letto qui? angular.io/docs/ts/latest/guide/router.html search for Route Guards Ecco un riferimento api per CanActivate: angular.io/docs/ts/latest/api/router/index/… come vedi può restituire booleano o osservabile <booleano>
mollwe

4
canActivate()può restituire un Observable, assicurati solo che Observablesia stato completato (es. observer.complete()).
Philip Bulley

1
@PhilipBulley e se l'osservabile emette più valori e poi si completa? Cosa fa la guardia? Quello che ho visto finora è l'uso take(1)dell'operatore Rx per ottenere il completamento del flusso, cosa succede se dimentico di aggiungerlo?
Felix

Risposte:


146

Dovresti aggiornare "@ angular / router" all'ultimo. ad es. "3.0.0-alpha.8"

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

Se hai domande, chiedimi!


3
Vale la pena sottolineare che questo funziona con le promesse in un modo molto simile. Per la mia implementazione, supponendo che isLoggedIn()sia un Promise, puoi farlo isLoggedIn().then((e) => { if (e) { return true; } }).catch(() => { return false; });Spero che questo aiuti i futuri viaggiatori!
kbpontius

6
Ho dovuto aggiungereimport 'rxjs/add/observable/of';
marc_aragones

1
non è una buona risposta ora IMO .. non fornisce dettagli di ciò che sta accadendo lato server ... non è aggiornato in alpha! ... non segue questa best practice .. angular.io/docs/ts/latest/guide/… .. vedi la mia risposta aggiornata di seguito (si spera un giorno sopra)
danday74

1
canActivate dovrebbe eseguire nuovamente Observable <boolean>
Yoav Schniederman

Grande aiuto :) Grazie
gschambial

57

Aggiornamento della risposta di Kery Hu per Angular 5 e RxJS 5.5 in cui l' catchoperatore è deprecato. Ora dovresti usare l'operatore catchError insieme agli operatori pipe e 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);
      })
    );
  }   

}

Ho un'altra chiamata XHR dopo isLoggedIn () e il risultato di XHR viene utilizzato nella seconda chiamata XHR. Come avere la seconda chiamata ajax che accetterà il primo risultato? L'esempio che hai fornito è abbastanza semplice, puoi farmi sapere come usare pipe () se ho anche un altro ajax.
Pratik

9

canActivate () accetta Observable<boolean>come valore restituito. La guardia attenderà che l'Osservabile si risolva e guarderà il valore. Se 'true' passerà il controllo, altrimenti (qualsiasi altro dato o errore lanciato) rifiuterà la rotta.

Puoi usare l' .mapoperatore per trasformare il Observable<Response>in Observable<boolean>così:

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

1
che ne dici di un blocco di cattura? il blocco catch viene chiamato se è un 401 giusto?
danday74

1
In angolare 4 non funziona. Ha bisogno di un posto per definire un tipo generico.
gtzinos

2

L'ho fatto in questo modo:

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

Come puoi vedere, sto inviando una funzione di fallback a userService.auth cosa fare se la chiamata http non riesce.

E in userService ho:

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

risposta davvero buona in termini di funzione .map () - davvero molto buona :) - non ha usato il callback di fallback - invece mi sono iscritto all'auth Observable nel metodo canActivate - grazie mille per l'idea
danday74

0

Questo potrebbe aiutarti

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

-1

CanActivate funziona con Observable ma non riesce quando vengono effettuate 2 chiamate come CanActivate: [Guard1, Guard2].
Qui se restituisci un Observable di false da Guard1, anche questo controllerà Guard2 e consentirà l'accesso alla route se Guard2 restituisce true. Per evitare ciò, Guard1 dovrebbe restituire un booleano invece di Observable di boolean.


-10

in canActivate (), puoi restituire una proprietà booleana locale (l'impostazione predefinita è false nel tuo caso).

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

Quindi, nel risultato del LoginService, puoi modificare il valore di quella proprietà.

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

sei un genio, signore.
Kien Nguyen Ngoc
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.