Angular2 canActivate () ruft die asynchrone Funktion auf

80

Ich versuche, Angular2-Router-Schutzvorrichtungen zu verwenden, um den Zugriff auf einige Seiten in meiner App einzuschränken. Ich verwende die Firebase-Authentifizierung. Um zu überprüfen, ob ein Benutzer bei Firebase angemeldet ist, muss ich .subscribe()das FirebaseAuthObjekt mit einem Rückruf aufrufen . Dies ist der Code für die Wache:

import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AngularFireAuth } from "angularfire2/angularfire2";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";

@Injectable()
export class AuthGuard implements CanActivate {

    constructor(private auth: AngularFireAuth, private router: Router) {}

    canActivate(route:ActivatedRouteSnapshot, state:RouterStateSnapshot):Observable<boolean>|boolean {
        this.auth.subscribe((auth) => {
            if (auth) {
                console.log('authenticated');
                return true;
            }
            console.log('not authenticated');
            this.router.navigateByUrl('/login');
            return false;
        });
    }
}

Wenn Sie zu einer Seite navigieren, auf der sich der Schutz befindet authenticated, oder not authenticateddie auf der Konsole gedruckt wird (nach einiger Verzögerung warten Sie auf die Antwort von Firebase). Die Navigation wird jedoch nie abgeschlossen. Wenn ich nicht angemeldet bin, werde ich zur /loginRoute weitergeleitet. Das Problem, das ich habe, ist return true, dass dem Benutzer die angeforderte Seite nicht angezeigt wird. Ich gehe davon aus, dass dies daran liegt, dass ich einen Rückruf verwende, aber ich kann nicht herausfinden, wie ich es anders machen soll. Irgendwelche Gedanken?

Evan Salter
quelle
Importable wie folgt importieren -> {Observable} aus 'rxjs / Observable' importieren;
Carlos Pliego

Antworten:

120

canActivatemuss eine zurückgeben Observable, die abgeschlossen ist:

@Injectable()
export class AuthGuard implements CanActivate {

    constructor(private auth: AngularFireAuth, private router: Router) {}

    canActivate(route:ActivatedRouteSnapshot, state:RouterStateSnapshot):Observable<boolean>|boolean {
        return this.auth.map((auth) => {
            if (auth) {
                console.log('authenticated');
                return true;
            }
            console.log('not authenticated');
            this.router.navigateByUrl('/login');
            return false;
        }).first(); // this might not be necessary - ensure `first` is imported if you use it
    }
}

Es returnfehlt ein und ich benutze map()statt subscribe()weil subscribe()gibt ein Subscriptionnicht ein zurückObservable

Günter Zöchbauer
quelle
Können Sie zeigen, wie diese Klasse in anderen Komponenten verwendet wird?
Nicht sicher was du meinst. Sie verwenden dies für Routen, nicht für Komponenten. Siehe angle.io/docs/ts/latest/guide/router.html#!#guards
Günter Zöchbauer
Das Observable läuft in meinem Fall nicht. Ich sehe keine Konsolenausgabe. Wenn ich jedoch Boolesche Werte bedingt zurückgebe (wie in den Dokumenten), wird die Konsole protokolliert. Ist this.auth ein einfaches Observable?
Cortopy
@cortopy authist ein Wert, der vom Observable ausgegeben wird (möglicherweise nur trueoder false). Das Observable wird ausgeführt, wenn der Router es abonniert. Möglicherweise fehlt in Ihrer Konfiguration etwas.
Günter Zöchbauer
1
@ günter-zöchbauer ja, danke dafür. Ich wusste nicht, dass ich einen Abonnenten abonniere. Vielen Dank für die Antwort! Es funktioniert großartig
Cortopy
25

Sie können Observableden asynchronen Logikteil verwenden. Hier ist der Code, den ich zum Beispiel teste:

import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { DetailService } from './detail.service';

@Injectable()
export class DetailGuard implements CanActivate {

  constructor(
    private detailService: DetailService
  ) {}

  public canActivate(): boolean|Observable<boolean> {
    if (this.detailService.tempData) {
      return true;
    } else {
      console.log('loading...');
      return new Observable<boolean>((observer) => {
        setTimeout(() => {
          console.log('done!');
          this.detailService.tempData = [1, 2, 3];
          observer.next(true);
          observer.complete();
        }, 1000 * 5);
      });
    }
  }
}
KobeLuo
quelle
2
Das ist eigentlich eine gute Antwort, die mir wirklich geholfen hat. Obwohl ich eine ähnliche Frage hatte, löste die akzeptierte Antwort mein Problem nicht. Dieser tat
Konstantin
Eigentlich ist das die richtige Antwort !!! Eine gute Möglichkeit, die canActivate-Methode zu verwenden, die eine asynchrone Funktion aufruft.
Danilo
17

canActivatezurückgeben kann einen PromiseEntschluss , dass ein booleanzu

paulsouche
quelle
12

Sie können true | false als Versprechen zurückgeben.

import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
import {Observable} from 'rxjs';
import {AuthService} from "../services/authorization.service";

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private router: Router, private authService:AuthService) { }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
  return new Promise((resolve, reject) => {
  this.authService.getAccessRights().then((response) => {
    let result = <any>response;
    let url = state.url.substr(1,state.url.length);
    if(url == 'getDepartment'){
      if(result.getDepartment){
        resolve(true);
      } else {
        this.router.navigate(['login']);
        resolve(false);
      }
    }

     })
   })
  }
}
Schurkenjunge
quelle
1
Das neue Promise-Objekt rettet mich: D Danke.
Canmustu
Vielen Dank. Diese Lösung wartet, bis der API-Anruf antwortet und dann umleitet. Perfekt.
Philip Enc
6

Um die beliebteste Antwort zu erweitern. Die Auth-API für AngularFire2 hat sich etwas geändert. Dies ist eine neue Signatur, um einen AngularFire2 AuthGuard zu erhalten:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { AngularFireAuth } from 'angularfire2/auth';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

@Injectable()
export class AuthGuardService implements CanActivate {

  constructor(
    private auth: AngularFireAuth,
    private router : Router
  ) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):Observable<boolean>|boolean {
    return this.auth.authState.map(User => {
      return (User) ? true : false;
    });
  }
}

Hinweis: Dies ist ein ziemlich naiver Test. Sie können die Benutzerinstanz in der Konsole protokollieren, um festzustellen, ob Sie einen Test anhand eines detaillierteren Aspekts des Benutzers durchführen möchten. Sollte aber zumindest dazu beitragen, Routen vor Benutzern zu schützen, die nicht angemeldet sind.

Rudi Strydom
quelle
4

In der neuesten Version von AngularFire funktioniert der folgende Code (bezogen auf die beste Antwort). Beachten Sie die Verwendung der "Pipe" -Methode.

import { Injectable } from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
import {AngularFireAuth} from '@angular/fire/auth';
import {map} from 'rxjs/operators';
import {Observable} from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthGuardService implements CanActivate {

  constructor(private afAuth: AngularFireAuth, private router: Router) {
  }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return this.afAuth.authState.pipe(
      map(user => {
        if(user) {
          return true;
        } else {
          this.router.navigate(['/login']);
          return false;
        }
      })
    );
  }
}

Ajitesh
quelle
Ich habe 1 weiteren XHR-Aufruf nach isLoggedIn () und das Ergebnis von XHR wird im 2. XHR-Aufruf verwendet. Wie bekomme ich einen 2. Ajax-Anruf, der für das 1. Ergebnis akzeptiert wird? Das Beispiel, das Sie gegeben haben, ist ziemlich einfach. Können Sie mir bitte mitteilen, wie ich die Karte verwenden soll, wenn ich auch einen anderen Ajax habe?
Pratik
2

In meinem Fall musste ich mit unterschiedlichem Verhalten umgehen, abhängig vom Antwortstatusfehler. So funktioniert es bei mir mit RxJS 6+:

@Injectable()
export class AuthGuard implements CanActivate {

  constructor(private auth: AngularFireAuth, private router: Router) {}

  public canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | boolean {
    return this.auth.pipe(
      tap({
        next: val => {
          if (val) {
            console.log(val, 'authenticated');
            return of(true); // or if you want Observable replace true with of(true)
          }
          console.log(val, 'acces denied!');
          return of(false); // or if you want Observable replace true with of(true)
        },
        error: error => {
          let redirectRoute: string;
          if (error.status === 401) {
            redirectRoute = '/error/401';
            this.router.navigateByUrl(redirectRoute);
          } else if (error.status === 403) {
            redirectRoute = '/error/403';
            this.router.navigateByUrl(redirectRoute);
          }
        },
        complete: () => console.log('completed!')
      })
    );
  }
}

In einigen Fällen könnte dies nicht funktionieren, zumindest den nextTeil der tapBetreiber . Entfernen Sie es und fügen Sie altes Gut mapwie unten hinzu:

  public canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | boolean {
    return this.auth.pipe(
      map((auth) => {
        if (auth) {
          console.log('authenticated');
          return true;
        }
        console.log('not authenticated');
        this.router.navigateByUrl('/login');
        return false;
      }),
      tap({
        error: error => {
          let redirectRoute: string;
          if (error.status === 401) {
            redirectRoute = '/error/401';
            this.router.navigateByUrl(redirectRoute);
          } else if (error.status === 403) {
            redirectRoute = '/error/403';
            this.router.navigateByUrl(redirectRoute);
          }
        },
        complete: () => console.log('completed!')
      })
    );
  }
mpro
quelle