Max Mumford
Max Mumford

Reputation: 2642

Angular2 Resolve before CanActivate

I have an Authentication service that implements the Resolve interface to return an observable that is resolved once a user's authentication state is retrieved from the server.

This same authentication service implements the CanActivate interface to block users from accessing a component if they are not logged in.

Currently, the canActivate function is triggered before the resolve function is resolved, meaning it checks the user's login state before the login state has been retrieved from the server.

My question is, how can I prevent the canActivate function being called until resolve is resolved, or otherwise is there another way I can achieve what I want?

Thanks Max.

Upvotes: 28

Views: 30972

Answers (3)

Abdullah Sohail
Abdullah Sohail

Reputation: 435

There is a solution to this,

As stated above the resolver always runs after the guard even though the resolver maybe defined at parent level and guard be defined at child level (there is a dirty hack to get around this though) consider the example,

...
{
            path: ':id',
            resolve: { authData: AuthenticationResolverService },
            children:[
              {
                path: 'edit',
                component: ProgramCreateEditComponent,
                children: [
                  {
                    path: 'general',
                    component: ProgramGeneralInfoComponent,
                    canActivate: [AuthGuard],
                  },
                ]
              }
            ]
}
...

you see even in this particular case the AuthGuard shall run before the AuthenticationResolverService, now one solution as stated above would be to simply move the AuthenticationResolverService logic for fetching authentication data to AuthGuard since the guard is also capable of consuming observables(return Observable), however this would be bad in a sense that everytime the guard runs you will fetch authentication data from the server.

However if i introduce a Parent Guard let's say ResolverGuard (it imitates our resolver service) and apply it at the parent route and since Parent Guard shall always run before the Child Guard, we can move our authentication data fetching logic to the parent guard and this would work!,

{
            path: ':id',
            canActivate:[ResolverGuard],
            children:[
              {
                path: 'edit',
                component: ProgramCreateEditComponent,
                children: [
                  {
                    path: 'general',
                    component: ProgramGeneralInfoComponent,
                    canActivate: [AuthGuard],
                  },
                ]
              }
            ]
}

so in this way we can make our ParentGuard imitate as a sort of resolver service, but you may say that how do i get the resolver data Authdata(in our case) then? well its simple you can introduce another service(or any other place) in that case and simply pass data to it while resolving data in parent guard ResolverGuard(in this case) and then get access to that data later on in a resolver or the component.

Upvotes: 3

Jeffrey Roosendaal
Jeffrey Roosendaal

Reputation: 7157

CanActivate always runs before resolve by design..

..because the resolver should only run after the CanActivate guards have done their job (authentication, cleanup, ..). When you follow this pattern (see below), there is usually little reason to reverse or change this order.


1. CanActivate (authentication):

  • Authenticate/verify user.
  • Get tokens needed to query/get data from a server or API.
  • Use either a Promise or Observable to make sure the resolvers wait for the CanActivate guards to complete.

2. Resolve (get data):

  • When all guards have passed, then it's time for the resolvers to run.
  • Get data when you're sure the user is authenticated.
  • Get data from the server using the tokens, etc.
  • Use either a Promise or Observable to make sure the components are loaded when the resolve data is available.

3. constructor/OnInit (use data):

  • Get the data from the resolver (with, for example, ActivatedRoute.snapshot.data).
  • Render the data in your template or send it to a service.

If, somehow, you really need to wait for the resolvers to complete, just move your code from a guard to a resolver.

You can read more about this in this thread on GitHub.

Upvotes: 47

j2L4e
j2L4e

Reputation: 7060

canActivate() can return a boolean, Promise<boolean> or Observable<boolean>.

If you return Promise or Observable, the component can't be navigated to until they are resolved to true.

Upvotes: 14

Related Questions