Angular 4 - canActivate observable not invoked

时间:2017-06-22 10:46:05

标签: angular typescript rxjs observable

我正在尝试使用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

3 个答案:

答案 0 :(得分:21)

使用RxJs验证AuthService

这是我从AngularJs的承诺转换为Angular的Observable模式时遇到的困难之一。你看到承诺是拉动通知,而观察者是推送通知。因此,您必须重新考虑您的AuthService,以便它使用推送模式。即使在我编写工作Observables时,我一直在考虑拉动。我不禁停止思考。

使用承诺模式更容易。创建AuthService时,它将创建一个解析为“未登录”的promise,或者它将创建一个“还原已记录状态”的异步保证。然后,您可以使用名为isLoggedIn()的方法来返回该承诺。这使您可以轻松处理显示用户数据和收到用户数据之间的延迟。

AuthService作为推送服务

现在,我们切换到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的问题

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更好

我们想要记住当前用户,但在我们知道是否有用户之前不会发出任何内容。 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时,我们希望等待,直到用户状态已知

CanActivate示例

现在可以更轻松地编写重定向到登录屏幕或允许路由更改的用户警卫。

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