如何在带有角度的canActivate中调用异步方法?
我正在尝试为Angular 4.2.4中的管理路由实现canActivate保护.
I am trying to implement a canActivate guard for an admin route in Angular 4.2.4.
基于此处的堆栈问题: canActivate问题,我想我是正确地做一切.但是,a,我似乎无法使事情正常进行.
Based off this stack question here: canActivate Question, I think I'm doing everything correctly. But alas, I can't seem to get things to work.
这是模块防护:
@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异步调用中获得响应.在控制台中,我总能感觉到寒冷.好像路由器从不订阅我的观察者吗?我想根据我的http响应返回一个布尔值.我怎样才能做到这一点?
I can never get the response from my http async call. I always get what looks to be a cold observable in the console. It's as if the router never subscribes to my observable? I would like to return a boolean based off what my http response is. How can I achieve this?
我正在从警卫人员中调用 verifyAdminUser
方法.该服务是根模块上的一个模块.防护措施可保护对延迟加载的模块的访问.
I am calling the verifyAdminUser
method from my guard. The service is a sinlgeton on the root module. The guard is protecting access to a lazy-loaded module.
下面,我在路由中包括了我在使用防护装置的位置.
Below I have included where I am using the guard in the routing.
路由模块:
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运算符中的响应未定义.
The trouble is when the user tries to navigate to the dashboard module, canActivate always returns false because the response in my switchMap operator is undefined.
我在代码中重构并简化了以下两种方法,现在一切正常.现在,我试图了解原因.这是调整后的方法:
I refactored and simplified the following two methods in my code, and now everything works just fine. Now, I'm trying to understand why. Here are the adjusted to methods:
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出色答案,我得以创建一个派生解决方案.这是我重构的两种方法:
Thanks to @BeetleJuice excellent answer below, I was able to create a derivative solution. Here are the two methods I refactored:
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});
}
请注意此行,取自 verifyUserRole
console.log(token);// Log out "PromiseObservable...
因此 token
是可以观察到的,但是您将其作为字符串放在下一行,因此服务器可能会拒绝该请求
So token
is an observable, but you're treating it as a string on the very next line so the server will probably reject that request
let headers = new Headers({ 'Authorization': 'Bearer ' + token})
您在 verifyAdminUser()
中误用了 .map
运算符. map
仅应与同步功能一起使用.例如:
You're misusing the .map
operator in verifyAdminUser()
. map
should only be used with synchronous functions. For example:
// this works if user.id is a property available immediately
return this.getToken().map(user => user.id)
map
不应与异步功能一起使用.例如:
map
should not be used with asynchronous functions. For instance:
// this fails since the return value is an Observable that will resolve later
return this.getToken().map(user => Observable.fromPromise(...))
地图
立即返回.结果,沿着链条传递的不是您期望的令牌本身,而是将来会生成令牌的Observable.这就是为什么 console.log(token)
给您"PromiseObservable"的原因.您需要一个运算符,它将等待其中的可观察值产生,然后将发出的值传递给下一个运算符.使用 switchMap
map
returns immediately. As a result, what gets passed down the chain is not the token itself as you expected, but rather an Observable that will produce the token in the future. That's whyconsole.log(token)
gave you "PromiseObservable". What you need is an operator that will wait for the observable in it to produce, then pass the emitted value to the next operator. Use switchMap
//this will work; switchMap waits for Obervable.fromPromise
// to emit before continuing
return this.getToken().switchMap(user => Observable.fromPromise(...))
因此,基本上,在 verifyAdminUser
的第3行和第4行中,用 switchMap
替换 map
.您还可以通过执行相反的操作来简化 verifyAdminUser
的最后一行:change
So basically, replace map
with switchMap
in lines 3 and 4 of verifyAdminUser
. You can also simplify the last line of verifyAdminUser
by doing the reverse: change
.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保护.因此,我可以将 canActivate
替换为 canLoad
(如果我想保护整个模块),或者将 canActivate
防护装置移至特定的路由在 DashboardRoutingModule
中具有 component:
属性(我猜是这个名字)
Also, you are using canActivate
incorrectly. This is meant to guard against the activation of a component. To guard against the loading of a lazy-loaded module, use canLoad guard. So I would either replace canActivate
with canLoad
(if I want to protect the entire module), or move the canActivate
guard to the specific route that has the component:
property within DashboardRoutingModule
(I'm guessing at the name)