Angular 2 - Routing - CanActivate work with Observable

94

Mam AuthGuard (używany do routingu), który implementuje CanActivate .

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

Mój problem polega na tym, że wynik CanActivate zależy od wyniku http-get - usługa logowania zwraca Observable .

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

Jak mogę to połączyć - sprawić, by CanActivate zależało od stanu zaplecza?

# # # # # #

EDYCJA: Proszę zwrócić uwagę, że to pytanie pochodzi z 2016 roku - zastosowano bardzo wczesną fazę kątownika / routera.

# # # # # #

Philipp
źródło
2
Czytałeś tutaj? angular.io/docs/ts/latest/guide/router.html szukaj Route Guards Oto odniesienie do interfejsu API dla CanActivate: angular.io/docs/ts/latest/api/router/index/ ... jak widzisz, może zwrócić albo boolean lub Observable <boolean>
mollwe
4
canActivate()może zwrócić Observable, po prostu upewnij się, że Observablezakończyło się (tj. observer.complete()).
Philip Bulley,
1
@PhilipBulley co, jeśli obserwowalny emituje więcej wartości, a następnie kończy? Co robi strażnik? To, co do tej pory widziałem, to użycie take(1)operatora Rx, aby osiągnąć kompletność strumienia, a co jeśli zapomnę go dodać?
Felix

Odpowiedzi:

148

Należy zaktualizować „@ angular / router” do najnowszej. np. „3.0.0-alfa.8”

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

Jeśli masz jakieś pytania, zapytaj mnie!

Kery Hu
źródło
3
Warto zaznaczyć, że działa to z obietnicami w bardzo podobny sposób. Na moje życie, zakładając, isLoggedIn()to Promisemożna zrobić isLoggedIn().then((e) => { if (e) { return true; } }).catch(() => { return false; });Mam nadzieję, że to pomoże przyszłych turystów!
kbpontius
6
Musiałem dodaćimport 'rxjs/add/observable/of';
marc_aragones
1
nie jest teraz dobrą odpowiedzią IMO .. nie podaje żadnych szczegółów tego, co się dzieje po stronie serwera ... jest nieaktualny w wersji alpha! ... to nie jest zgodne z tą najlepszą praktyką .. angular.io/docs/ts/latest/guide/ ... .. zobacz moją zaktualizowaną odpowiedź poniżej (mam nadzieję, że jeden dzień powyżej)
danday74
1
canActivate powinno ponownie zdjąć Observable <boolean>
Yoav Schniederman
Świetna pomoc :) Dzięki
gschambial
60

Aktualizacja odpowiedzi Kery Hu na Angular 5+ i RxJS 5.5, gdzie catchoperator jest przestarzały. Należy teraz używać operatora catchError w połączeniu z operatorami potoku i 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
źródło
Mam jeszcze 1 wywołanie XHR po isLoggedIn (), a wynik XHR jest używany w drugim wywołaniu XHR. Jak mieć drugie wywołanie Ajax, które zaakceptuje pierwszy wynik? Przykład, który podałeś, jest całkiem prosty, czy możesz mi dać znać, jak używać pipe (), jeśli mam też inny ajax.
Pratik
bardzo przydatne również dla kątowej 10
Ruslan López
10

canActivate () akceptuje Observable<boolean>jako zwróconą wartość. Strażnik będzie czekał, aż Obserwowalny rozwiąże i spojrzy na wartość. Jeśli `` prawda '' przejdzie kontrolę, w przeciwnym razie (wszelkie inne dane lub wyrzucony błąd) odrzuci trasę.

Można użyć .mapoperatora do przekształcenia Observable<Response>się Observable<boolean>w taki sposób:

canActivate(){
    return this.http.login().map((res: Response)=>{
       if ( res.status === 200 ) return true;
       return false;
    });
}
mp3por
źródło
1
a co z blokiem catch? blok catch jest wywoływany, jeśli jest to 401, prawda?
danday74
1
W kątowej 4 nie działa. Musi gdzieś zdefiniować typ ogólny.
gtzinos,
3

Zrobiłem to w ten sposób:

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

Jak widać, wysyłam funkcję rezerwową do userService.auth, co zrobić, jeśli wywołanie http nie powiedzie się.

A w userService mam:

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
źródło
naprawdę dobra odpowiedź pod względem funkcji .map () - naprawdę bardzo dobra :) - nie użyłem funkcji zwrotnej - zamiast tego zasubskrybowałem auth Obserwowane w metodzie canActivate - bardzo dziękuję za pomysł
danday74
0

To może ci pomóc

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
źródło
-1

CanActivate działa z Observable, ale kończy się niepowodzeniem, gdy wykonywane są 2 wywołania, takie jak CanActivate: [Guard1, Guard2].
Tutaj, jeśli zwrócisz Observable lub false z Guard1, wtedy również sprawdzi Guard2 i zezwoli na dostęp do trasy, jeśli Guard2 zwróci true. Aby tego uniknąć, Guard1 powinien zwrócić wartość logiczną zamiast Observable of boolean.

Arjunsingh
źródło
-10

w canActivate () możesz zwrócić lokalną właściwość boolowską (w twoim przypadku domyślnie jest to false).

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

Następnie w wyniku usługi LoginService możesz zmodyfikować wartość tej właściwości.

//...
this.loginService.login().subscribe(success => this._canActivate = true);
Nguyễn Việt Trung
źródło
jest pan geniuszem, proszę pana.
Kien Nguyen Ngoc