问题
I have set an authentication guard with firebase email and password login, the issue is that it automatically trigger 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]);
});
}
}
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?
回答1:
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
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.
Auth Service:
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:
@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);
}
}
Component:
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]);
});
}
}
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.
Update:
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;
});
Hopefully that helps!
来源:https://stackoverflow.com/questions/52300405/angular-5-authentication-guard-automatically-navigate-to-specified-compnonent