与Angular 2应用程序和Angular 2路由器的异步性问题

时间:2016-09-23 12:18:44

标签: asynchronous angular rxjs angular2-routing ngrx

我在角度2应用中遇到了一个棘手的异步问题问题。

当我在用户的浏览器中重新加载/引导应用程序时,我基本上尝试从后端重新水化/重新加载信息(想想F5 /刷新)。问题是在后端异步方法返回结果之前,会调用路由器保护并阻塞...

我从根组件的ngOnInit方法重新加载信息,如下所示:

来自root组件:

  ngOnInit() {
    //reloadPersonalInfo returns an Observable
    this.sessionService.reloadPersonalInfo()
      .subscribe();
  }

来自sessionService

  reloadPersonalInfo() {
    //FIRST: the app execution flow gets here
    let someCondition: boolean = JSON.parse(localStorage.getItem('someCondition'));
    if (someCondition) {
      return this.userAccountService.retrieveCurrentUserAccount()
        .switchMap(currentUserAccount => {
          //THIRD: and finally, the execution flow will get there and set the authenticated state to true (unfortunately too late)...
          this.store.dispatch({type: SET_AUTHENTICATED});
          this.store.dispatch({type: SET_CURRENT_USER_ACCOUNT, payload: currentUserAccount});
          return Observable.of('');
        });
    }
    return Observable.empty();
  }

问题是我有一个路由器CanActivate保护,如下所示:

  canActivate() {
    //SECOND: then the execution flow get here and because reloadPersonalInfo has not completed yet, isAuthenticated will return false and the guard will block and redirect to '/signin'
    const isAuthenticated = this.sessionService.isAuthenticated();
    if (!isAuthenticated) {
      this.router.navigate(['/signin']);
    }
    return isAuthenticated;
  }

来自sessionService

  isAuthenticated(): boolean {
    let isAuthenticated = false;
    this.store.select(s => s.authenticated)
      .subscribe(authenticated => isAuthenticated = authenticated);
    return isAuthenticated;
  }

所以回顾一下:

  1. FIRST:reloadPersonalInfo上的sessionService方法由根组件ngOnInit调用。执行流程进入此方法。
  2. SECOND:与此同时,调用路由器防护并发现authenticated状态为false(因为reloadPersonalInfo尚未完成,因此未设置{{1}状态为authenticated
  3. 第三:true完成太晚并将reloadPersonalInfo状态设置为authenticated(但路由器警卫已被阻止)。
  4. 有人可以帮忙吗?

    编辑1 :让我强调重要的true状态是商店中的状态;它由此行设置:authenticated

    编辑2 :我将条件从this.store.dispatch({type: SET_AUTHENTICATED});更改为authenticated,以减少混淆。以前,还有另一个名为someCondition ...

    的状态变量

    编辑3 :我已将authenticated方法返回类型更改为isAuthenticated()而不是Observable<boolean>(遵循Martin的建议)并调整{{1方法如下:

    boolean

    来自canActivate

     canActivate() {
        return this.sessionService.isAuthenticated().map(isAuthenticated => {
          if (isAuthenticated) {
            return true;
          }
          this.router.navigate(['/signin']);
          return false;
        });
      }
    

    不幸的是,没有任何区别......

    有人可以就如何排除这种异步性问题提出建议吗?

3 个答案:

答案 0 :(得分:2)

应该有两种可能的方法来解决这个问题:

解决方案1 ​​

最快的方法是将你的isAuthenticated区分为2个而不是3个状态,这样你就可以将另外一条信息编码到状态中:在引导时(当服务器没有响应时)已收到),客户无法确定其凭证/代币是否有效,因此状态应该正确地是&#34;未知&#34;。

首先,您必须将商店中authenticated的初始状态更改为null(您也可以选择undefined,甚至根据个人喜好使用数字)。然后你只需要为你的后卫添加一个.filter,这样几乎可以让守卫保持不动状态&#34;:

canActivate() {
    return this.sessionService.isAuthenticated()
        .filter(isAuthenticated => isAuthenticated !== null) // this will cause the guard to halt until the isAuthenticated-question/request has been resolved
        .do(isAuth => (if (!isAuth) {this.router.navigate(['/signin'])}));
}

解决方案2

第二种解决方案非常相似,但不是将第三种状态编码为authenticated,而是在商店中添加一个名为authRequestRunning的新标记,设置为{{1正在进行身份验证请求,并在完成后设置为true。然后你的警卫看起来会略有不同:

false

使用解决方案#2,您可能会有更多代码。 并且在canActivate() { return this.sessionService.isAuthenticated() .switchMap(isAuth => this.sessionService.isAuthRequestRunning() .filter(running => !running) // don't emit any data, while the request is running .mapTo(isAuth); ) .do(isAuth => (if (!isAuth) {this.router.navigate(['/signin'])})); } - 状态更新之前,您必须小心authRequestRunning设置为false 优先

编辑:我已经在解决方案#2中编辑了代码,因此设置运行状态和身份验证状态的顺序不再重要。< / p>

我之所以使用解决方案#2,是因为在大多数情况下,这样的状态标志已经存在,并且用于显示加载指示器或类似的东西。

答案 1 :(得分:0)

为什么在拨打retrieveCurrentUserAccount电话之前未设置身份验证?根据{{​​1}}

中的值,您似乎已经知道您的用户是否经过身份验证
localStorage

<强>更新

尝试以下,

if (isAuthenticated) {
      // set before you give a async call.
      this.store.dispatch({type: SET_AUTHENTICATED});
      return this.userAccountService.retrieveCurrentUserAccount()
        .switchMap(currentUserAccount => {
          //THIRD: and finally, the execution flow will get there and set the authenticated state to true (unfortunately too late)...             
          this.store.dispatch({type: SET_CURRENT_USER_ACCOUNT, payload: currentUserAccount});
          return Observable.of('');
        });
    }

以下是Plunker!!

希望这会有所帮助!!

答案 2 :(得分:0)

canActivate本身可以返回一个Observable。

不是在canActivate中返回布尔结果,而是返回isAuthenticated Observable。