最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to call an async method in canActivate with angular? - Stack Overflow

programmeradmin2浏览0评论

I am trying to implement a canActivate guard for an admin route in Angular 4.2.4.

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.

Here is the module guard:

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

Here is my czauth service:

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

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?

EDIT:

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.

Routing Module:

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 { }

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.

EDIT 2:

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

Solution:

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

I am trying to implement a canActivate guard for an admin route in Angular 4.2.4.

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.

Here is the module guard:

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

Here is my czauth service:

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

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?

EDIT:

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.

Routing Module:

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 { }

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.

EDIT 2:

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

Solution:

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});
  }
Share Improve this question edited Jun 25, 2017 at 2:33 calbear47 asked Jun 25, 2017 at 0:21 calbear47calbear47 1,2113 gold badges21 silver badges42 bronze badges 5
  • having a hard time getting what you're asking. (1)How are the two code excerpts you posted related? (2) which function exactly fails to behave as you'd like? – BeetleJuice Commented Jun 25, 2017 at 0:28
  • @BeetleJuice I have edited my question, does this help explain it better? – calbear47 Commented Jun 25, 2017 at 0:44
  • What's your thinking behind using mergeMap at the end of verifyAdminUser? – BeetleJuice Commented Jun 25, 2017 at 2:24
  • If I use a map, I get a Type error "Type 'Observable<Observable<boolean>>' is not assignable to type 'Observable<boolean>' because I need to merge the Observable.of with the current stream....but I think you're right. Going with just a map at the end and returning a boolean is cleaner. – calbear47 Commented Jun 25, 2017 at 2:32
  • For those who are looking for angular6 stackoverflow./a/51363905/536388 – Maertz Commented Jul 16, 2018 at 14:20
Add a ment  | 

1 Answer 1

Reset to default 8

Pay attention to this line, taken from verifyUserRole

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

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

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 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(...))

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(...))

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

with

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

Also, you are using canActivate incorrectly. This is meant to guard against the activation of a ponent. 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 ponent: property within DashboardRoutingModule (I'm guessing at the name)

See the docs

发布评论

评论列表(0)

  1. 暂无评论