Angular 4-未激活canActivate observable [英] Angular 4 - canActivate observable not invoked

查看:72
本文介绍了Angular 4-未激活canActivate observable的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在Angular 2/4中实现 canActivate 使用RxJS可观察对象.我已经阅读了另一个SO问题.使用以下代码,当应用启动时,我的 canActivate 方法只能运行一次,但是当 isLoggedIn 可观察到的触发新的 hello 时,就不会再打印值.

I am trying to implement canActivate in Angular 2/4 using RxJS observables. I have already read another SO question on this. With the following code, my canActivate method works only once, when the app launches, but hello is never printed again when the isLoggedIn observable triggers new values.

canActivate(): Observable<boolean> {
  return this.authService.isLoggedIn().map(isLoggedIn => {
    console.log('hello');
    if (!isLoggedIn) {
      this.router.navigate(['/login']);
    }
    return isLoggedIn;
  }).first();
}

或此版本无法正常运行:

or this version is not working as well:

canActivate(): Observable<boolean> {
  return this.authService.isLoggedIn().map(isLoggedIn => {
    console.log('hello');
    if (isLoggedIn) {
      this.router.navigate(['/']);
    }
    return !isLoggedIn;
  });
}

但是,此代码可以正常工作:

However, it works fine with this code:

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));
  });
}

我在第一段代码中做错了什么?

What am I doing wrong in the first piece of code ?

这是 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 .

推荐答案

使用RxJ进行AuthService的挑战

这是我从AngularJs的承诺转换为Angular的Observable模式时遇到的事情之一.您会看到promise是pull通知,observables是push通知.因此,您必须重新考虑AuthService,以便它使用推送模式.即使我写了《 Observables》,我也一直在思考如何拉动.我不停地想着拉力.

Challenges of AuthService with RxJs

This was one of the things I struggled with when switching from AngularJs's promises to Angular's Observable pattern. You see promises are pull notifications and observables are push notifications. As such, you have to rethink your AuthService so that it uses a push pattern. I kept thinking in terms of pulling even when I wrote working Observables. I couldn't stop thinking in terms of pulling.

有了诺言模式,就更容易了.创建AuthService时,它将创建一个已解决为未登录"的承诺,或者将创建一个异步应许恢复已登录状态"的承诺.然后,您可以有一个名为 isLoggedIn()的方法,该方法将返回该承诺.这样一来,您就可以轻松处理显示用户数据与接收用户数据之间的延迟.

With a promise pattern it was easier. When the AuthService was created it would either create a promise that resolved to "not logged in" or it would create an async promise that would "restore logged state". You could then have a method named isLoggedIn() that would return that promise. That allowed you to easily handle delays between showing user data and when you receive user data.

现在,我们切换到Observables,将动词 是" 更改为何时" .进行此小更改可帮助您重新考虑事情的运行方式.因此,让我们将"isLoggedIn"重命名为"whenLoggedIn()",这将是一个Observable,当用户进行身份验证时会发出数据.

Now, we switch to Observables and the verb "is" needs to be changed to "when". Making this small change helps you re-think how things are going to work. So lets rename "isLoggedIn" to "whenLoggedIn()" which will be an Observable that emits data when a user authenticates.

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 时,它发出订阅以确认新用户已通过身份验证.

When a user passed to setUser it's emitted to subscribes that a new user has been authenticated.

上面介绍了一些需要解决的问题.

The above introduces several problems that need to be fixed.

  • 订阅 whenLoggedIn 将会永远监听新用户.拉流永远不会完成.
  • 没有当前状态"的概念.之前的 setUser 被推送给订阅者后会丢失.
  • 它仅告诉您何时对用户进行身份验证.如果没有当前用户,则不会.
  • subscribing to whenLoggedIn will listen for new users forever. The pull stream is never completed.
  • There is no concept of "current state". The previous setUser is lost after being pushed to subscribers.
  • It only tells you when a user is authenticated. Not if there is no current user.

我们可以通过从 Subject 切换到 BehaviorSubject 来解决部分问题.

We can fix some of this by switching from Subject to 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());

这更接近我们想要的东西.

This is much closer to what we want.

  • BehaviorSubject 将始终为每个 new 订阅发出最后一个值.
  • 在收到第一个值后,在 subscribe auto unsubscribe 中添加了
  • whenLoggedIn().first().如果我们不使用 BehaviorSubject ,这将阻止,直到有人叫 setUser ,这可能永远不会发生.
  • BehaviorSubject will always emit the last value for each new subscription.
  • whenLoggedIn().first() was added to subscribe and auto unsubscribe after the first value is received. If we didn't use BehaviorSubject this would block until someone called setUser which might never happen.

BehaviorSubject 不适用于AuthService,我将在此处演示此示例代码.

BehaviorSubject doesn't work for AuthService and I'll demonstrate with this sample code here.

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;
     }
}

这是您的代码中出现问题的方式.

Here's how the problem would appear in your code.

// example
let auth = new AuthService("my_token", tokenService);
auth.whenLoggedIn().first().subscribe(console.log);

上面创建了一个带有令牌的新AuthService来还原用户会话,但是当它运行控制台时,它只会显示"null".

The above creates a new AuthService with a token to restore the user session, but when it runs the console just prints "null".

之所以会这样,是因为创建的 BehaviorSubject 的初始值为 null ,并且将在HTTP调用完成之后执行恢复用户会话的操作.AuthService将继续发出 null ,直到恢复会话为止,但是当您要使用路由激活器时,这是一个问题.

This happens because BehaviorSubject is created with an initial value of null, and the operation to restore the user session is going to happen later after the HTTP call is complete. AuthService will continue to emit null until the session is restored, but that's a problem when you want to use route activators.

我们想记住当前用户,但是在知道是否有用户之前不发出任何信息. ReplaySubject 是此问题的答案.

We want to remember the current user, but not emit anything until we know if there is a user or not. ReplaySubject is the answer to this problem.

这是您将如何使用它.

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 值并取消订阅.

The above will not wait until the first value is emitted by whenLoggedIn. It will get the first value and unsubscribe.

ReplaySubject 之所以起作用,是因为它记住了 1 项或什么也不发出.重要的是 nothing 部分.当我们在 canActivate 中使用AuthService时,我们想等待直到用户状态 为止.

ReplaySubject works because it remembers the 1 items or emits nothing. It's the nothing part that is important. When we use AuthService in canActivate we want to wait until a user state is known.

现在,这使编写重定向到登录屏幕或允许更改路线的用户防护变得容易得多.

This now makes it a lot easier to write a user guard that redirects to a login screen or allows the route to change.

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);
      }

如果存在用户会话,这将产生一个true或false的Observable.它还会阻止路由器更改,直到知道该状态为止(即我们是否正在从服务器中获取数据?).

This will yield an Observable of true or false if there is a user session. It will also block router change until that state is known (i.e. are we fetching data from the server?).

如果没有用户数据,它还会将路由器重定向到登录屏幕.

It will also redirect the router to the login screen if there is no user data.

这篇关于Angular 4-未激活canActivate observable的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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