我正在尝试使用RxJS observable在Angular 2/4中实现canActivate
。我已经阅读了another SO question。使用以下代码时,我的canActivate
方法仅在应用启动时有效,但hello
observable触发新值时,isLoggedIn
永远不再打印。
canActivate(): Observable<boolean> {
return this.authService.isLoggedIn().map(isLoggedIn => {
console.log('hello');
if (!isLoggedIn) {
this.router.navigate(['/login']);
}
return isLoggedIn;
}).first();
}
或此版本无效:
canActivate(): Observable<boolean> {
return this.authService.isLoggedIn().map(isLoggedIn => {
console.log('hello');
if (isLoggedIn) {
this.router.navigate(['/']);
}
return !isLoggedIn;
});
}
但是,它可以正常使用此代码:
canActivate(): Observable<boolean> {
return Observable.create(obs => {
this.authService.isLoggedIn().map(isLoggedIn => {
console.log('hello');
if (isLoggedIn) {
this.router.navigate(['/']);
}
return !isLoggedIn;
}).subscribe(isLoggedIn => obs.next(isLoggedIn));
});
}
我在第一段代码中做错了什么?
编辑:这是isLoggedIn
实施
@LocalStorage(AuthService.JWT_TOKEN_KEY)
private readonly token: string;
private tokenStream: Subject<string>;
public isLoggedIn(): Observable<boolean> {
if (!this.tokenStream) {
this.tokenStream = new BehaviorSubject(this.token);
this.storage.observe(AuthService.JWT_TOKEN_KEY)
.subscribe(token => this.tokenStream.next(token));
}
return this.tokenStream.map(token => {
return token != null
});
}
使用ngx-webstorage
。和RxJS BehaviorSubject
。
答案 0 :(得分:21)
这是我从AngularJs的承诺转换为Angular的Observable模式时遇到的困难之一。你看到承诺是拉动通知,而观察者是推送通知。因此,您必须重新考虑您的AuthService,以便它使用推送模式。即使在我编写工作Observables时,我一直在考虑拉动。我不禁停止思考。
使用承诺模式更容易。创建AuthService时,它将创建一个解析为“未登录”的promise,或者它将创建一个“还原已记录状态”的异步保证。然后,您可以使用名为isLoggedIn()
的方法来返回该承诺。这使您可以轻松处理显示用户数据和收到用户数据之间的延迟。
现在,我们切换到Observables,动词 “是”需要在“时更改为”。进行这一小改动有助于您重新思考事情的发展方向。因此,我们将“isLoggedIn”重命名为“whenLoggedIn()”,这将是一个在用户进行身份验证时发出数据的Observable。
class AuthService {
private logIns: Subject = new Subject<UserData>();
public setUser(user: UserData) {
this.logIns.next(user);
}
public whenLoggedIn(): Observable<UserData> {
return this.logIns;
}
}
// example
AuthService.whenLoggedIn().subscribe(console.log);
AuthService.setUser(new UserData());
当用户传递给setUser
时,它会发出以预订新用户的身份验证。
以上介绍了需要解决的几个问题。
whenLoggedIn
将永远收听新用户。拉流永远不会完成。setUser
将丢失。我们可以通过从Subject
切换到BehaviorSubject
来解决部分问题。
class AuthService {
private logIns: Subject = new BehaviorSubject<UserData>(null);
public setUser(user: UserData) {
this.logIns.next(user);
}
public whenLoggedIn(): Observable<UserData> {
return this.logIns;
}
}
// example
AuthService.whenLoggedIn().first().subscribe(console.log);
AuthService.setUser(new UserData());
这更接近我们想要的。
BehaviorSubject
将始终为每个新订阅发出最后一个值。whenLoggedIn().first()
被添加到 subscribe 和 auto unsubscribe 。如果我们没有使用BehaviorSubject
,那么阻止,直到某个名为setUser
的人可能永远不会发生。 BehaviorSubject
不适用于AuthService,我将在此处演示此示例代码。
class AuthService {
private logIns: Subject = new BehaviorSubject<UserData>(null);
public constructor(userSessionToken:string, tokenService: TokenService) {
if(userSessionToken) {
tokenService.create(userSessionToken).subscribe((user:UserData) => {
this.logIns.next(user);
});
}
}
public setUser(user: UserData) {
this.logIns.next(user);
}
public whenLoggedIn(): Observable<UserData> {
return this.logIns;
}
}
以下是问题在您的代码中的显示方式。
// example
let auth = new AuthService("my_token", tokenService);
auth.whenLoggedIn().first().subscribe(console.log);
上面创建了一个带有令牌的新AuthService来恢复用户会话,但是当它运行时,控制台只打印“null”。
这是因为BehaviorSubject
创建的初始值为null
,恢复用户会话的操作将在HTTP调用完成后发生。在会话恢复之前,AuthService将继续发出null
,但是当您想要使用路由激活器时,这是一个问题。
我们想要记住当前用户,但在我们知道是否有用户之前不会发出任何内容。 ReplaySubject
就是解决这个问题的答案。
以下是您将如何使用它。
class AuthService {
private logIns: Subject<UserData> = new ReplaySubject(1);
public constructor(userSessionToken:string, tokenService: TokenService) {
if(userSessionToken) {
tokenService.create(userSessionToken).subscribe((user:UserData) => {
this.logIns.next(user);
}, ()=> {
this.logIns.next(null);
console.error('could not restore session');
});
} else {
this.logIns.next(null);
}
}
public setUser(user: UserData) {
this.logIns.next(user);
}
public whenLoggedIn(): Observable<UserData> {
return this.logIns;
}
}
// example
let auth = new AuthService("my_token", tokenService);
auth.whenLoggedIn().first().subscribe(console.log);
在whenLoggedIn
发出第一个值之前,上面不会等待。它将获得first
值并取消订阅。
ReplaySubject
有效,因为它会记住1
项目或没有任何内容。这是 nothing 部分非常重要。当我们在canActivate
中使用AuthService时,我们希望等待,直到用户状态已知。
现在可以更轻松地编写重定向到登录屏幕或允许路由更改的用户警卫。
class UserGuard implements CanActivate {
public constructor(private auth: AuthService, private router: Router) {
}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.auth.whenLoggedIn()
.first()
.do((user:UserData) => {
if(user === null) {
this.router.navigate('/login');
}
})
.map((user:UserData) => !!user);
}
如果存在用户会话,这将产生一个Observable为true或false。它还会阻止路由器更改,直到该状态已知(即我们是否从服务器获取数据?)。
如果没有用户数据,它还会将路由器重定向到登录屏幕。
答案 1 :(得分:0)
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.service.callServiceMethod())
.map(result => {
return true;
}).catch(err => {
return Observable.of(false);
});
return Observable.of(true);
}
答案 2 :(得分:0)
这就是我的成就
import { Injectable, Inject, Optional } from "@angular/core";
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";
import { Observable } from "rxjs/Rx";
import { AuthService, MyLogin, FacebookLogin, GoogleLogin } from "./auth.service";
@Injectable()
export class AuthGuard implements CanActivate {
constructor() {
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
// return this.typeSelector(SigninComponent.loginTypeProperty);
let isLogged: boolean = AuthService.loggedIn;
window.alert('isLogged =' + isLogged);
return isLogged;
}
}
另一种情况是我必须更改密码:
import { Injectable, Inject, Optional } from "@angular/core";
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";
import { Observable } from "rxjs/Rx";
import { NewPasswordAuthService } from "./new-password.auth.service";
import { Router, ActivatedRoute } from '@angular/router';
import { IPwCookie } from './IPwCookie.interface';
@Injectable()
export class NewPasswordGuard implements CanActivate {
constructor(private authService: NewPasswordAuthService, private router: Router, private route: ActivatedRoute,)
{
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
console.log('query params' + JSON.stringify(route.params));
let hash:string = route.params.hash;
let userId: number = route.params.userId;
console.log('query params string' + JSON.stringify(route.params.hash));
return this.authService.activate(hash)
.map(
(): boolean => {
console.log('check passed =' + NewPasswordAuthService.isAuthenticated());
let passwordCookie:IPwCookie = {
hash: hash,
userId: userId
};
localStorage.setItem('password_hash', JSON.stringify(passwordCookie));
return NewPasswordAuthService.isAuthenticated();
},
(error) => {console.log(error)}
);
}
}
据我所知,你应该尝试在map函数中添加一个返回类型,这是给我带来问题的一件事。如果不向map函数添加返回类型,则在此上下文中它不起作用。
所以只需要做.map(():boolean =&gt; {} ...)