web-dev-qa-db-fra.com

Angular2 canActivate () appelant une fonction async

J'essaie d'utiliser des gardes de routeur Angular2 pour restreindre l'accès à certaines pages de mon application. J'utilise l'authentification Firebase. Afin de vérifier si un utilisateur est connecté à Firebase, je dois appeler .subscribe() sur l'objet FirebaseAuth avec un rappel. C'est le code pour la garde:

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

Lorsque vous naviguez sur une page protégée, authenticated ou not authenticated est imprimé sur la console (après un certain délai d'attente de la réponse de firebase). Cependant, la navigation n'est jamais terminée. De plus, si je ne suis pas connecté, je suis redirigé vers la route /login. Donc, le problème que j'ai est que return true n'affiche pas la page demandée à l'utilisateur. Je suppose que c'est parce que j'utilise un rappel, mais je suis incapable de comprendre comment le faire autrement. Des pensées?

38
Evan Salter

canActivate doit renvoyer une Observable complétant:

@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
    }
}

Il manque une return et j'utilise map() au lieu de subscribe() car subscribe() renvoie une Subscription et non une Observable

72
Günter Zöchbauer

canActivate peut retourner une Promise qui résout aussi une boolean

11
paulsouche

Vous pouvez utiliser Observable pour gérer la partie logique asynchrone. Voici le code que je teste par exemple: 

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

Pour développer la réponse la plus populaire. L'API Auth pour AngularFire2 a quelque peu changé. Ceci est la nouvelle signature pour réaliser un AngularFire2 AuthGuard:

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

Note: Ceci est un test assez naïf. Vous pouvez consigner l'instance de l'utilisateur pour voir si vous souhaitez tester certains aspects plus détaillés de l'utilisateur. Mais devrait au moins aider à protéger les itinéraires contre les utilisateurs qui ne sont pas connectés.

3
Rudi Strydom

Vous pouvez retourner true | false comme une promesse.

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

     })
   })
  }
}
2
Muhammad Aslam

Dans la version la plus récente de AngularFire, le code suivant fonctionne (lié à la meilleure réponse). Notez l'utilisation de la méthode "pipe".

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

1
Ajitesh