Reputation: 13
I'm trying to create a route guard that keeps some lazy-loaded module safe, I have a auth service with BehaviorSubject that holds current user and JWT token
When I call the guard it first gets the default value of the current user and only on secend try it allow the user to get to the route.
auth.service.ts:
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpResponse, HttpHeaders } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { map, skip, takeLast, last } from 'rxjs/operators';
import { User } from '../shared/user';
import { environment } from '../../environments/environment';
@Injectable()
export class AuthService {
private loginUserToken: BehaviorSubject<string>;
private currentUser: BehaviorSubject<User>;
constructor(private http: HttpClient) {
// service init
this.loginUserToken = new BehaviorSubject(undefined);
this.currentUser = new BehaviorSubject(undefined);
this.loginUserToken.next(this.getTokenFromLocalStorege());
if (this.loginUserToken.value != null) {
this.getUserFromToken();
}
}
/**
* getLoginUser
*/
public getLoginUserAsObservable() {
return this.currentUser.asObservable();
}
public getLoginUserTokenAsObservable() {
return this.loginUserToken.asObservable();
}
public async login(user: User): Promise<any> {
// tslint:disable-next-line:no-shadowed-variable
return new Promise<any>(async (resolve: any, reject: any) => {
try {
const result: any = await this.http.post(`${environment.server}/api/auth/login`, user).toPromise();
if (result.massageCode === 1) {
reject('bedUsername');
} else if (result.massageCode === 2) {
reject('bed password');
} else {
this.loginUserToken.next(result.token);
this.getUserFromToken();
this.saveTokenToLocalStorege(result.token);
resolve();
}
} catch (error) {
reject('error');
}
});
}
public getUserFromToken(): void {
const headers = new HttpHeaders({
'x-access-token': this.loginUserToken.value
});
this.http.get(`${environment.server}/api/auth/userFromToken`, { headers }).toPromise()
.then((data: User) => {
this.currentUser.next(data);
})
.catch((error) => {
console.log(error);
});
}
public isLogin(): Promise<boolean> {
return new Promise((resolve, reject) => {
this.currentUser.asObservable()
.subscribe((data) => {
if (data) {
resolve(true);
} else {
resolve(false);
}
}).unsubscribe();
});
}
public saveTokenToLocalStorege(token: string): void {
localStorage.setItem('chanToken', token);
}
public getTokenFromLocalStorege(): string {
return localStorage.getItem('chanToken');
}
public removeTokenFromLocalStrege(): void {
localStorage.removeItem('chanToken');
}
}
auth.guaed.ts:
import { Injectable } from '@angular/core';
import { CanActivate, CanLoad, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from '../auth.service';
import { Route } from '@angular/compiler/src/core';
import { last, map } from 'rxjs/operators';
@Injectable()
export class AuthGuard implements CanLoad {
constructor(private authSerivce: AuthService, private router: Router) { }
canLoad(route: Route): boolean | Observable<boolean> | Promise<boolean> {
console.log('use gruad');
return this.authSerivce.isLogin();
}
}
Upvotes: 0
Views: 1332
Reputation: 2823
That's because how a BehaviourSubject
works. When you Subscribe
to a BehaviourSubject
it immediately returns the last value. That's why a BehaviourSubject
needs a default value.
In your case as soon the AuthGuard
gets activated it will call your method isLogin
which subscribes to currentUser
and it will return undefined
(Depending on the sequence of javascript execution). Basically your AuthGuard
will not wait for your function getUserFromToken()
to finish.
You can easily solve this solution when you use ReplaySubject
instead of BehaviourSubject
.
private currentUser: ReplaySubject<User> = new ReplaySubject(1)
Initialize ReplaySubject
with the value of 1 and it will cache your current user, but will not fire when there no user exist.
For now on your Authguard
will wait for a valid currentUser
value and will not fire when currentUser
is undefined
Upvotes: 3