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

查看:53
本文介绍了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<T>的形式返回,也需要使用then()来访问结果/成功,并使用catch()来相应地处理组件中的错误.您将根本无法在这样的if()语句中检查结果.从根本上讲,如果您尝试检查在if语句中返回Promise<T>的函数,则无论是否在毫秒后触发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<User>实时跟踪用户的身份验证状态,该身份可以在canActivate()之类的方法中使用.这是您可以采用的在auth服务中公开Observable<User>的方法,可用于检查登录状态,获取当前用户,甚至在模板中使用async管道显示诸如用户头像图像之类的内容. .有RxJS运算符,例如taptakemap,以帮助避免不必要地保持订阅处于活动状态.这些方法大多返回Observable<T>,您还可以在其他服务或组件中通过管道传递其他运算符/操作,以充分利用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']));
  }
}

Auth Guard:

Auth Guard:

@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组件生命周期且不依赖jQuery的基于Angular的Bootstrap组件/库.

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天全站免登陆