I got stuck recently with Angular route guards. CanActive runs only once when the page is loaded and does not run on route change within the guarded route. I think this was chan
Faced the same issue and all I was able to find on issue are few closed issues on Github with Angular devs statements that such behavior "is by design".
So what I ended up doing is subscribing on navigation events in app.component and firing AuthGuard check there:
constructor(
private router: Router,
private route: ActivatedRoute,
private authGuard: AuthGuard,
) {}
ngOnInit() {
this.router.events
.subscribe(event => {
if (event instanceof RoutesRecognized) {
this.guardRoute(event);
}
}));
}
private guardRoute(event: RoutesRecognized): void {
if (this.isPublic(event)) {
return;
}
if (!this.callCanActivate(event, this.authGuard)) {
return;
}
}
private callCanActivate(event: RoutesRecognized, guard: CanActivate) {
return guard.canActivate(this.route.snapshot, event.state);
}
private isPublic(event: RoutesRecognized) {
return event.state.root.firstChild.data.isPublic;
}
AuthGuard is rather standard:
@Injectable()
export class AuthGuard implements CanActivate{
constructor(private auth: AuthService, private router: Router) { }
canActivate(): Promise<boolean> {
return this.auth.isLoggedInPromise()
.then(isLoggedIn => {
if (!isLoggedIn) {
this.router.navigate(["/login"]);
}
return isLoggedIn;
});
}
}
And public routes should be configured like this:
{
path: "login",
component: LoginComponent,
data: { isPublic: true }
}
The plus of such implementation is that everything is protected by default and public route should be configured explicitly, which will reduce the possibility of leaving some routes unprotected. Will also refactor this into some kind of service, to be able to use it across multiple apps.
Inspired by this answer.
The issue with subscribing to router events is that the navigation has already been started and the history has been updated, which makes it hard to prevent the navigation in a reliable way as a guard does.
But Angular has learned to provide you with a way to configure how guards and resolvers should behave directly in your routes.ts
:
export const routes: Routes = [
{
path: '404',
component: NotFoundComponent
},
{
path: '',
canActivate: [AuthGuard],
runGuardsAndResolvers: 'always',
children: [
....
]
}
]
Here's the docs: https://angular.io/api/router/RunGuardsAndResolvers
There's a nice blogpost explaining your options: https://juristr.com/blog/2019/01/Explore-Angular-Routers-runGuardsAndResolvers/