Reputation: 17491
I have routes setup like so:
export const APP_ROUTES: Routes = [
{
path: '',
resolve: {
user: CurrentUserResolver
},
children: [
{
path: ':sectionKey',
canActivate: [SectionAccessGuard],
children: [
{
path: 'dashboard',
component: DashboardComponent
}
]
}
]
}
]
Where the parent route will retrieve the user via a HTTP call, and I'd like my sub-route :sectionKey
to only be activated if the current user has access to it. The issue is, it appears my canActivate SectionAccessGuard
is called prior to the snapshot being fully populated:
@Injectable()
export default class SectionAccessGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
console.log(route.data);
console.log(route);
return true;
}
}
In the console, the first entry will have nothing; however, the 2nd entry will eventually have the user
field populated.
I know the canActivate
method can return an Observable<boolean>
as well, but I don't see any hooks on how to wait until the resolve completes.
What am I missing here? I feel like this should be pretty straight forward. I'm currently using angular v4.0.0-rc.2
Upvotes: 10
Views: 2413
Reputation: 17491
It's been about 8 months since I posted this so I'll give an update on where we ended up:
Storing data in the router through resolves started to become very awkward depending on where the component was (this.route.parent.parent.parent.data.user
), so we're no longer using resolves, and instead using NgRx to store that kind of information.
Our guards then are in charge of triggering the fetching of the data, as well as waiting until it's complete. Something like this:
@Injectable()
export class AuthenticationGuard implements CanActivate {
constructor(private router: Router, private store: Store<ApplicationState>) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
this.store.dispatch(new AuthenticateAction());
return this.store
.select('auth')
.filter((s) => !!s && !s.authenticating)
.map((s) => s.authenticated)
.take(1);
}
}
Where there are a couple flags in the state (authenticating and authenticated) that tell the guard where we're at in the process.
Upvotes: 1
Reputation: 992
I'm struggling with this too and I'd like to see more documentation surrounding this from the Angular team. It seems canActivate does not wait to start running until after the parent is finished, but resolve does.
You can change your can activate code into another resolve, because a child resolve does wait for a parent resolve. If it is changed to resolve you will have access to the user through route.parent.data.user and you can then make a call to router.navigate to leave the section if the user isn't authorized.
Another approach is to set up a service that can be injected into both the resolve guard and the canActivate guard. You'd have to set up an observable from the resolve guard and subscribe to it in the canActivate guard.
Upvotes: 0
Reputation: 12376
I'm not sure if it's possible to enforce the guard to work after the resolver completes, but I'd try to move the code from the resolver into canActivate().
When the observable that gets the current user info returns the data, apply your logic to check if the user is allowed to navigate to the route.
Upvotes: 0