2018年8月24日金曜日

canActivate で interval() は使えない

Angular6 で AuthGuard とか Resolve とか使い始めて RxJS6 にも慣れてきて面白くなってきて、AuthGuard の canActivate で timeout() 使ったらサーバへの問い合わせを定期的に行ってエラーが返ってきたときに画面遷移を行うってのができるのでは?と思ってググってもなかなか情報が見つからないので、とりあえずこんなふうに書いてみました。
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return interval(10000)
      .pipe(
        mergeMap(() => {
          return this.httpClient.get('/isAuthorized')
            .pipe(
              map(response => {
                return response.authorized;
              })
            );
        })
      );
トランスパイルは通りました。が、一回しか HTTP アクセスが発生しません。
デバッガで canActivate() を出たあとを追ってみたところ…

pipe(first()) \(^o^)/オワタ

https://github.com/angular/angular/blob/master/packages/router/src/pre_activation.ts#L248
  private runCanActivate(future: ActivatedRouteSnapshot): Observable<boolean> {
    const canActivate = future.routeConfig ? future.routeConfig.canActivate : null;
    if (!canActivate || canActivate.length === 0) return of (true);
    const obs = from(canActivate).pipe(map((c: any) => {
      const guard = this.getToken(c, future);
      let observable: Observable<boolean>;
      if (guard.canActivate) {
        observable = wrapIntoObservable(guard.canActivate(future, this.future));
      } else {
        observable = wrapIntoObservable(guard(future, this.future));
      }
      return observable.pipe(first());
    }));
    return andObservables(obs);
  }
timeout() の Observable を返しても最初の1回目の Observable<boolean> しか使わない、ということですね。
まあ、Guard は Route の許可をするかどうかの位置にあるものなので、一度表示した Route を拒否することは想定していない実装ということではないかと思います。
おとなしくコンポーネント内で interval() 回して画面遷移させるようにします…
そうそう、interval() の unsubscribe() も忘れずに…