Angular 5:身份验证守卫自动导航到指定的组件 [英] Angular 5: Authentication guard automatically navigates to specified component

查看:17
本文介绍了Angular 5:身份验证守卫自动导航到指定的组件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我设置了一个带有 firebase 电子邮件和密码登录的身份验证防护,问题是它会自动触发到指定组件的路由.

I have set an authentication guard with firebase email and password login, the issue is that it automatically triggers the route to the specified component.

我已经实现了身份验证保护并将其设置在正确的模块提供程序中(因为我的应用程序中有许多提供程序).这是我的身份验证服务:

I have implemented the authentication guard and set it in the correct module provider (because I have many providers in my application). This is my authentication service:

import { Injectable } from '@angular/core';
import { Router, Route ,CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild, CanLoad } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { SimpleAuthService } from './simple-auth.service';

@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad {
  constructor(private authService: SimpleAuthService) { }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
    const url: string = state.url;
    return this.checkLogin(url);
  }

  canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
    return this.canActivate(route, state);
  }

  canLoad(route: Route): Observable<boolean> {
    const url: string = route.path;
    return this.checkLogin(url);
  }

  checkLogin(url: string): Observable<boolean> {
    return this.authService.isLoggedIn(url);
  }
}

这是我注入它的组件类(登录组件):

and this is the component class where I inject it (the login component):

import { Component, OnInit, ElementRef } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { SimpleAuthService } from '../../core/simple-auth.service';

declare var $: any;

@Component({
    selector: 'app-login-cmp',
    templateUrl: './login.component.html'
})

export class LoginComponent implements OnInit {
    private toggleButton: any;
    private sidebarVisible: boolean;
    private nativeElement: Node;
    private email  ='user@dev.org'
    private password ='useruser';


    constructor(private element: ElementRef, public authService: SimpleAuthService,
        private router: Router, private route: ActivatedRoute) {
            if (this.authService.login(this.email,this.password)) {
                this.router.navigate(['dashboard']);
            } else {
                this.nativeElement = element.nativeElement;
                this.sidebarVisible = false;
            }
        }

    ngOnInit() {
        this.login(this.email, this.password);
        var navbar : HTMLElement = this.element.nativeElement;
        this.toggleButton = navbar.getElementsByClassName('navbar-toggle')[0];

        setTimeout(function() {
            // after 1000 ms we add the class animated to the login/register card
            $('.card').removeClass('card-hidden');
        }, 700);
    }
    sidebarToggle() {
        var toggleButton = this.toggleButton;
        var body = document.getElementsByTagName('body')[0];
        var sidebar = document.getElementsByClassName('navbar-collapse')[0];
        if (this.sidebarVisible == false) {
            setTimeout(function() {
                toggleButton.classList.add('toggled');
            }, 500);
            body.classList.add('nav-open');
            this.sidebarVisible = true;
        } else {
            this.toggleButton.classList.remove('toggled');
            this.sidebarVisible = false;
            body.classList.remove('nav-open');
        }
    }

    login(username: string, password: string): void {
        this.authService.login(username, password).then(_ => {
          const redirectUrl: string = this.authService.redirectUrl || 'dashboard';
          this.router.navigate([redirectUrl]);
        });
      }
}

这是我使用 firebase 制作的身份验证服务:

this my authentication service made using firebase:

import { Injectable } from '@angular/core';
import { Router, Route ,CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild, CanLoad } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { AngularFireAuth } from 'angularfire2/auth';
import { User, UserCredential } from '@firebase/auth-types';
import { take, map, tap } from 'rxjs/operators';

@Injectable()
export class SimpleAuthService {
  user: Observable<User>;
  redirectUrl: string;

  constructor(private afAuth: AngularFireAuth, private router: Router) {
    this.user = this.afAuth.authState;
  }

  getUser(): Observable<User> {
    return this.user.pipe(take(1));
  }

  isLoggedIn(redirectUrl: string): Observable<boolean> {
    return this.user.pipe(
      take(1),
      map(authState => !!authState),
      tap(authenticated => {
        if (!authenticated) {
          this.redirectUrl = redirectUrl;
          this.router.navigate(['/']);
        }
      })
    );
  }

  login(username: string, password: string): Promise<UserCredential> {
    return this.afAuth.auth.signInWithEmailAndPassword(username, password);
  }

  logout(): Promise<boolean> {
    return this.afAuth.auth.signOut().then(() => this.router.navigate(['/login']));
  }
}

你知道我哪里出错了吗?

Do you have any idea where I made a mistake?

推荐答案

基本问题是您的 login() 方法是异步的,但您正在尝试检查结果,就好像它是一个同步方法.此外,login() 当前甚至不返回任何内容 void,因此不会有任何结果需要检查.无论哪种方式,即使结果作为 Promise 返回,您也需要使用 then() 来访问结果/成功,并且 catch() 在组件中相应地处理错误.您根本无法在这样的 if() 语句中检查结果.在基本层面上,如果您试图检查在 if 语句中返回 Promise 的函数,它总是为真,无论 catch() 是否为毫秒后触发.

The base issue is that your login() method is asynchronous, but you are attempting to check the result as if it was a synchronous method. In addition, login() isn't even returning anything currently void, so there wouldn't any result to check for. Either way, even if the result was returned as a Promise<T>, you'd need to use then() to access the results/success, and catch() to handle errors accordingly in the component. You simply will not be able to check the results in an if() statement like that. At a basic level, if you are attempting to check a function returning a Promise<T> in an if statement, it will always be truthy, regardless if catch() is triggered milliseconds later.

function login(): Promise<boolean> {
    return Promise.resolve(true);
}

if(login()) {} // this is always true regardless of `catch()` is triggered sometime in the future

使用 angularfire2,您可以将用户的身份验证状态实时跟踪为 Observable,可以在 canActivate() 等方法中使用.这是您可以采用的一种方法,它在 auth 服务中公开 Observable,可用于检查登录状态、获取当前用户,甚至在模板中显示类似用户的内容带有 async 管道的头像.有 RxJS 运算符,例如 taptakemap,可帮助避免不必要地保持订阅活动.使用这些方法主要返回 Observable,您可以另外在其他服务或组件中通过管道传输额外的操作符/操作,以充分利用 RxJS.

With angularfire2, you can track the authentication state of the user in realtime as an Observable<User> that can be used in methods such as canActivate(). Here is an approach you could take that exposes the Observable<User> in the auth service, that can be used to check login status, get the current user, or even in a template to display something like user avatar image with the async pipe. There are RxJS operators such as tap, take, and map to help avoid unnecessarily keeping subscriptions active. With these methods mostly returning Observable<T>, you can additionally pipe additional operators/actions in your other services or components to take full advantage of RxJS.

身份验证服务:

import { AngularFireAuth } from 'angularfire2/auth';
import { User, UserCredential } from '@firebase/auth-types';

@Injectable()
export class AuthService {
  user: Observable<User>;
  redirectUrl: string;

  constructor(private afAuth: AngularFireAuth, private router: Router) {
    this.user = this.afAuth.authState;
  }

  getUser(): Observable<User> {
    return this.user.pipe(take(1));
  }

  isLoggedIn(redirectUrl: string): Observable<boolean> {
    return this.user.pipe(
      take(1),
      map(authState => !!authState),
      tap(authenticated => {
        if (!authenticated) {
          this.redirectUrl = redirectUrl;
          this.router.navigate(['/login']);
        }
      })
    );
  }

  login(username: string, password: string): Promise<UserCredential> {
    return this.afAuth.auth.signInWithEmailAndPassword(email, password);
  }

  logout(): Promise<boolean> {
    return this.afAuth.auth.signOut().then(() => this.router.navigate(['/login']));
  }
}

身份验证保护:

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

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
    const url: string = state.url;
    return this.checkLogin(url);
  }

  canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
    return this.canActivate(route, state);
  }

  canLoad(route: Route): Observable<boolean> {
    const url: string = route.path;
    return this.checkLogin(url);
  }

  checkLogin(url: string): Observable<boolean> {
    return this.authService.isLoggedIn(url);
  }
}

组件:

export class LoginComponent implements OnInit {
  constructor(private router: Router, public authService: AuthService) { }

  ngOnInit() {
    this.login(this.email, this.password);
  }

  login(username: string, password: string): void {
    this.authService.login(username, password).then(_ => {
      const redirectUrl: string = this.authService.redirectUrl || '/some-default-route';
      this.router.navigate([redirectUrl]);
    });
  }
}

总体建议,您将希望更好地利用服务进行组件之间的通信.您在 JavaScript 和 jQuery 中大量使用 DOM 操作,这违背了 Angular 的目的,并且可能会导致问题,因为由于组件生命周期和渲染,元素在您预期时不可用.使用服务和 RxJS,您可以通过指令 ngClass 设置 CSS 类.查看关于 家长和孩子通过以下方式交流的文档一个服务,举例说明组件如何在基本级别进行通信.如果它是您需要的 Bootstrap,您应该考虑基于 Angular 的 Bootstrap 组件/库,这些组件/库与 Angular 组件生命周期挂钩,并且不依赖于 jQuery.

An overall recommendation, you will want to take better advantage services for communication between components. You are heavily using both DOM manipulation with JavaScript and jQuery, which defeats the purpose of Angular and will likely cause issues as elements will not be available when you expect due to component lifecycle and rendering. Using services and RxJS you can set CSS classes via directives ngClass. Check out the documentation on Parent and children communicate via a service for an example of how components can communicate at a basic level. It if it's Bootstrap you need, you should consider Angular based Bootstrap components/libraries that hook into the Angular component life cycle and do not depend on jQuery.

更新:

根据您提供的更新代码,您仍在尝试使用构造函数中的 if 语句检查异步 Promise login() 是否成功.这将始终导致重定向,因为 login() 正在返回一个真实值的承诺.而是尝试在 LoginComponent 中返回的承诺上使用 then()/catch() 来响应 Firebase 的 signInWithEmailAndPassword 的成功/错误():

Based on the updated code you provided, you are still attempting to check success of async Promise login() using an if statement in your constructor. This will always cause the redirect as login() is returning a promise which would be truthy value. Instead try using then()/catch() on the returned promise in your LoginComponent to respond to success/error of Firebase's signInWithEmailAndPassword():

this.authService.login(this.email, this.password)
  .then(result => this.router.navigate(['dashboard']))
  .catch(err => {
    this.nativeElement = element.nativeElement;
    this.sidebarVisible = false;
  });

希望有帮助!

这篇关于Angular 5:身份验证守卫自动导航到指定的组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆