Angular 2 - Enrutamiento - CanActivate trabajar con Observable

94

Tengo un AuthGuard (utilizado para enrutamiento) que implementa CanActivate .

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

Mi problema es que el resultado de CanActivate depende de un http-get-result; el LoginService devuelve un Observable .

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

¿Cómo puedo unirlos? ¿Hacer que CanActivate dependa de un estado de backend?

# # # # # #

EDITAR: Tenga en cuenta que esta pregunta es de 2016: se ha utilizado una etapa muy temprana de angular / enrutador.

# # # # # #

Philipp
fuente
2
¿Has leído aquí? angular.io/docs/ts/latest/guide/router.html búsqueda de Route Guards Aquí hay una referencia de API para CanActivate: angular.io/docs/ts/latest/api/router/index/… como ve, puede devolver booleano u observable <booleano>
mollwe
4
canActivate()puede devolver un Observable, solo asegúrese de que se Observablehaya completado (es decir observer.complete()).
Philip Bulley
1
@PhilipBulley ¿y si el observable emite más valores y luego se completa? ¿Qué hace el guardia? Lo que he visto hasta ahora es el uso del take(1)operador Rx para lograr la finalización de la secuencia. ¿Qué pasa si me olvido de agregarlo?
Felix

Respuestas:

146

Debe actualizar "@ angular / enrutador" a la última versión. por ejemplo, "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);
        });
    }   
}

Si tienes alguna duda, pregúntame!

Kery Hu
fuente
3
Vale la pena señalar que esto funciona con las promesas de una manera muy similar. Para mi implementación, asumiendo que isLoggedIn()es un Promise, puede hacerlo isLoggedIn().then((e) => { if (e) { return true; } }).catch(() => { return false; });.
kbpontius
6
Tuve que agregarimport 'rxjs/add/observable/of';
marc_aragones
1
no es una buena respuesta ahora en mi opinión ... no proporciona ningún detalle de lo que está sucediendo en el lado del servidor ... ¡está desactualizado en alfa! ... no sigue esta mejor práctica .. angular.io/docs/ts/latest/guide/… .. vea mi respuesta actualizada a continuación (con suerte un día más arriba)
danday74
1
canActivate debe retornar Observable <boolean>
Yoav Schniederman
Gran ayuda :) Gracias
gschambial
57

Actualización de la respuesta de Kery Hu para Angular 5 y RxJS 5.5 donde el catchoperador está en desuso. Ahora debería usar el operador catchError junto con los operadores de canalización y permitible .

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
fuente
Tengo 1 llamada XHR más después de isLoggedIn (), y el resultado de XHR se usa en la segunda llamada XHR. ¿Cómo tener una segunda llamada ajax que aceptará el primer resultado? El ejemplo que dio es bastante fácil, por favor, hágame saber cómo usar pipe () si también tengo otro ajax.
Pratik
9

canActivate () acepta Observable<boolean>como valor devuelto. El guardia esperará a que el Observable se resuelva y verá el valor. Si es 'verdadero', pasará la verificación, de lo contrario (cualquier otro dato o error arrojado) rechazará la ruta.

Se puede utilizar el .mapoperador para transformar el Observable<Response>de Observable<boolean>este modo:

canActivate(){
    return this.http.login().map((res: Response)=>{
       if ( res.status === 200 ) return true;
       return false;
    });
}
mp3por
fuente
1
¿qué pasa con un bloque de captura? el bloque de captura se llama si es un 401, ¿verdad?
danday74
1
En angular 4 no funciona. Necesita algún lugar para definir un tipo genérico.
gtzinos
2

Lo he hecho de esta manera:

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

Como puede ver, estoy enviando una función de respaldo a userService.auth qué hacer si falla la llamada http.

Y en userService tengo:

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
fuente
muy buena respuesta en términos de la función .map () - realmente muy buena :) - no usé la devolución de llamada alternativa - en su lugar me suscribí a la autenticación Observable en el método canActivate - muchas gracias por la idea
danday74
0

Esto puede ayudarte

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

CanActivate funciona con Observable pero falla cuando se realizan 2 llamadas como CanActivate: [Guard1, Guard2].
Aquí, si devuelve un Observable de falso de Guard1, también comprobará Guard2 y permitirá el acceso a la ruta si Guard2 devuelve verdadero. Para evitar eso, Guard1 debería devolver un booleano en lugar de Observable de boolean.

Arjunsingh
fuente
-10

en canActivate (), puede devolver una propiedad booleana local (por defecto es falso en su caso).

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

Y luego, en el resultado de LoginService, puede modificar el valor de esa propiedad.

//...
this.loginService.login().subscribe(success => this._canActivate = true);
Nguyễn Việt Trung
fuente
es un genio, señor.
Kien Nguyen Ngoc