簡體   English   中英

Angular 4 - canActivate observable not invoked

[英]Angular 4 - canActivate observable not invoked

我想實現canActivate在角2/4使用RxJS觀測。 我已經閱讀了另一個關於此的問題 使用以下代碼,我的canActivate方法僅在應用程序啟動時工作一次,但是當isLoggedIn observable觸發新值時,永遠不會再打印hello

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

使用RxJs驗證AuthService

這是我從AngularJs的承諾轉換為Angular的Observable模式時遇到的困難之一。 你看到承諾是拉動通知,而觀察者是推送通知。 因此,您必須重新考慮您的AuthService,以便它使用推送模式。 即使在我編寫工作Observables時,我一直在考慮拉動。 在拉動方面,我無法停止思考。

使用承諾模式更容易。 創建AuthService時,它將創建一個解析為“未登錄”的promise,或者它將創建一個“還原已記錄狀態”的異步保證。 然后,您可以使用名為isLoggedIn()的方法來返回該promise。 這使您可以輕松處理顯示用戶數據和收到用戶數據之間的延遲。

AuthService作為推送服務

現在,我們切換到Observables, 動詞 “is”需要更改為“when” 進行這一小改動有助於您重新思考事情的發展方向。 因此,我們將“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並在收到第一個值后自動取消訂閱 如果我們沒有使用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”。

發生這種情況是因為使用初始值null創建了BehaviorSubject ,並且在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項目或者什么都不發出。 不是重要的部分。 當我們在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。 它還會阻止路由器更改,直到該狀態已知(即我們是否從服務器獲取數據?)。

如果沒有用戶數據,它還會將路由器重定向到登錄屏幕。

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

這就是我的成就

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM