如何使用angular调用canActivate中的异步方法?

时间:2017-06-25 00:21:52

标签: javascript angular asynchronous rxjs

我正在尝试在Angular 4.2.4中为管理路由实现canActivate防护。

根据这个堆栈问题:canActivate Question,我认为我正确地做了一切。但是,唉,我似乎无法让事情发挥作用。

以下是模块守卫:

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private _store: Store<AppState>,
              private router: Router,
              private czauth: CzauthService) { }

  canActivate(route: ActivatedRouteSnapshot):Observable<boolean>{
    return this.czauth.verifyAdminUser().first();
  }
};

以下是我的czauth服务:

  getToken():Observable<firebase.User>{
    return this.af.idToken;
  }

  verifyUserRole(token){
      this.token = token;
      console.log(token);// Log out "PromiseObservable {_isScalar: false, promise: D, scheduler: undefined}"
      let headers = new Headers({ 'Authorization': 'Bearer ' + token});
      let options = new RequestOptions({ headers: headers });
      return this.http.get(environment.api+'/verifyadminuser', options).map((response)=>{return response.json()});
  }


  verifyAdminUser():Observable<boolean>{
    return this.getToken()
      .map((user)=>{return Observable.fromPromise(user.getIdToken())})
      .map((token)=>{return this.verifyUserRole(token)})
      .do((response)=>{console.log(response)})// Always returns "Observable {_isScalar: false, source: Observable, operator: MapOperator}"
      .switchMap((response:any)=>{ return response.status === 200 ? Observable.of(true) : Observable.of(false)});
  }

我永远无法从我的http异步调用中获得响应。我总是在控制台中看到看起来很冷的东西。好像路由器永远不会订阅我的observable一样?我想根据我的http响应返回一个布尔值。我怎样才能做到这一点?

编辑:

我从警卫处调用verifyAdminUser方法。该服务是根模块上的一个sinlgeton。警卫正在保护对延迟加载模块的访问。

下面我列出了我在路由中使用防护的地方。

路由模块:

const routes: Routes = [
  {path: '', loadChildren : './landing/landing.module#LandingModule'},
  {path: 'dashboard', loadChildren : './dashboard/dashboard.module#DashboardModule', canActivate: [ AuthGuard ]}
];

@NgModule({
  imports: [RouterModule.forRoot(routes, {preloadingStrategy: PreloadAllModules})],
  exports: [RouterModule]
})
export class AppRoutingModule { }

问题是当用户尝试导航到仪表板模块时,canActivate总是返回false,因为我的switchMap操作符中的响应未定义。

编辑2:

我在我的代码中重构并简化了以下两种方法,现在一切正常。现在,我试图了解原因。以下是调整方法:

  verifyUserRole(){
      let headers = new Headers({ 'Authorization': 'Bearer ' + this.token});
      let options = new RequestOptions({ headers: headers });
      return this.http.get(environment.api+'/verifyadminuser', options).map((response)=>{return response});
  }


  verifyAdminUser():Observable<boolean>{
    return this.getToken()
      .map((user)=>{
        user.getIdToken().then((token)=>this.token = token);
        return user;
      })
      .map(()=>{return this.verifyUserRole})
      .switchMap((response:any)=>{ return response ? Observable.of(true) : Observable.of(false)});
  }

解决方案:

感谢@BeetleJuice下面的优秀答案,我能够创建衍生解决方案。 以下是我重构的两种方法:

  verifyUserRole(token){
    this.token = token;
    let headers = new Headers({ 'Authorization': 'Bearer ' + token});
    let options = new RequestOptions({ headers: headers });
    return this.http.get(environment.api+'/verifyadminuser', options).map((response)=>{return response});
  }


  verifyAdminUser():Observable<boolean>{
    return this.getToken()
      .switchMap((user)=>{return Observable.fromPromise(user.getIdToken())})
      .switchMap((token)=>{return this.verifyUserRole(token)})
      .map((response:any)=>{ return response.status === 200 ? true : false});
  }

1 个答案:

答案 0 :(得分:5)

请注意这一行,取自verifyUserRole

console.log(token);// Log out "PromiseObservable...

所以token是一个可观察的,但是你把它当作下一行的字符串对待,所以服务器可能会拒绝该请求

let headers = new Headers({ 'Authorization': 'Bearer ' + token})

您在.map中滥用verifyAdminUser()运算符。 map只应与同步函数一起使用。例如:

// this works if user.id is a property available immediately
return this.getToken().map(user => user.id)

map不应与异步函数一起使用。例如:

// this fails since the return value is an Observable that will resolve later
return this.getToken().map(user => Observable.fromPromise(...))

map立即返回。因此,传递给链的内容不是您预期的令牌本身,而是将来会生成令牌的Observable。这就是console.log(token)给你“PromiseObservable”的原因。你需要的是一个等待其中的observable生成的运算符,然后将发出的值传递给下一个运算符。使用switchMap

//this will work; switchMap waits for Obervable.fromPromise 
// to emit before continuing
return this.getToken().switchMap(user => Observable.fromPromise(...))

所以基本上,在map的第3行和第4行中将switchMap替换为verifyAdminUser。您也可以通过反向简化verifyAdminUser的最后一行:更改

.switchMap((response:any)=>{ return response.status === 200 ? Observable.of(true) : Observable.of(false)})

// no need for switchMap because response.status is available immediately
.map(res => res.status===200? true: false)

此外,您错误地使用了canActivate。这是为了防止组件的激活。要防止加载延迟加载的模块,请使用canLoad guard。所以我要用canActivate替换canLoad(如果我想保护整个模块),或者将canActivate后卫移到具有component:属性的特定路由中DashboardRoutingModule(我猜这个名字)

See the docs