Angular 2 - Roteamento - Trabalho CanActivate com Observable

94

Eu tenho um AuthGuard (usado para roteamento) que implementa CanActivate .

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

Meu problema é que o CanActivate-result depende de um http-get-result - o LoginService retorna um Observable .

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

Como posso uni-los - fazer CanActivate depender de um estado de back-end?

# # # # # #

EDIT: Por favor, note que esta questão é de 2016 - um estágio muito inicial do angular / roteador foi usado.

# # # # # #

Philipp
fonte
2
Você leu aqui? angular.io/docs/ts/latest/guide/router.html pesquisa de guardas de rota Aqui está a referência de API para CanActivate: angular.io/docs/ts/latest/api/router/index/… como você vê, pode retornar booleano ou observável <boolean>
mollwe
4
canActivate()pode retornar um Observable, apenas certifique-se de que o Observablefoi concluído (ou seja. observer.complete()).
Philip Bulley
1
@PhilipBulley e se o observável emitir mais valores e depois completar? O que o guarda faz? O que tenho visto até agora é o uso do take(1)operador Rx para atingir a totalidade do fluxo. E se eu esquecer de adicioná-lo?
Felix

Respostas:

148

Você deve atualizar "@ angular / router" para o mais recente. por exemplo, "3.0.0-alpha.8"

modificar 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 você tiver alguma dúvida, pergunte-me!

Kery Hu
fonte
3
Vale ressaltar que isso funciona com as promessas de maneira muito semelhante. Para minha implementação, supondo que isLoggedIn()seja um Promise, você pode fazer isLoggedIn().then((e) => { if (e) { return true; } }).catch(() => { return false; });Espero que isso ajude os futuros viajantes!
kbpontius
6
Tive de adicionarimport 'rxjs/add/observable/of';
marc_aragones,
1
não é uma boa resposta agora IMO .. não fornece detalhes do que está acontecendo do lado do servidor ... está desatualizado no alpha! ... não segue esta prática recomendada .. angular.io/docs/ts/latest/guide/… .. veja minha resposta atualizada abaixo (espero que um dia acima)
danday74
1
canActivate deve voltar a executar Observable <boolean>
Yoav Schniederman
Grande ajuda :) Obrigado
gschambial
60

Atualizando a resposta de Kery Hu para Angular 5+ e RxJS 5.5 onde ocatch operador está obsoleto. Agora você deve usar o operador catchError em conjunto com os operadores 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);
      })
    );
  }   
  
}
Derek Hill
fonte
Tenho mais 1 chamada XHR após isLoggedIn () e o resultado de XHR é usado na segunda chamada XHR. Como fazer a 2ª chamada ajax que aceitará para o 1º resultado? O exemplo que você deu é muito fácil, por favor, deixe-me saber como usar o pipe () se eu tiver outro ajax também.
Pratik
muito útil também para angular 10
Ruslan López
10

canActivate () aceita Observable<boolean>como valor retornado. O guarda vai esperar que o Observável resolva e olhar para o valor. Se for 'verdadeiro', ele passará na verificação, senão (quaisquer outros dados ou erro lançado) rejeitará a rota.

Você pode usar o .mapoperador para transformar o Observable<Response>para Observable<boolean>like assim:

canActivate(){
    return this.http.login().map((res: Response)=>{
       if ( res.status === 200 ) return true;
       return false;
    });
}
mp3por
fonte
1
que tal um bloco de captura? o bloco catch é chamado se for um 401 certo?
danday74,
1
No angular 4 não funciona. Precisa de algum lugar para definir um tipo genérico.
gtzinos
3

Eu fiz desta forma:

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

Como você pode ver, estou enviando uma função de fallback para userService.auth, o que fazer se a chamada http falhar.

E em userService eu tenho:

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
fonte
resposta realmente boa em termos da função .map () - realmente muito boa :) - não usei o callback de fallback - em vez disso, me inscrevi no auth Observable no método canActivate - muito obrigado pela ideia
danday74
0

Isso pode te ajudar

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
fonte
-1

CanActivate funciona com Observable, mas falha quando 2 chamadas são feitas como CanActivate: [Guard1, Guard2].
Aqui, se você retornar um Observable of false de Guard1, ele também verificará em Guard2 e permitirá o acesso à rota se Guard2 retornar true. Para evitar isso, Guard1 deve retornar um booleano em vez de Observable de boolean.

Arjunsingh
fonte
-10

em canActivate (), você pode retornar uma propriedade booleana local (o padrão é false no seu caso).

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

E então, no resultado do LoginService, você pode modificar o valor dessa propriedade.

//...
this.loginService.login().subscribe(success => this._canActivate = true);
Nguyễn Việt Trung
fonte
você é um gênio, senhor.
Kien Nguyen Ngoc