I'm working in an Angular 9 app and I'm trying to load (or not) an specific module only when my app state (ngrx) have a property != null
First, I have an AuthGuard in my routes but with canActivate. So I want the 'dashboard' module only loads when mt AppState have a token
Here is my route file
const routes: Routes = [
{
path: '',
component: AppLayoutComponent,
canActivate: [ AuthGuard ],
children: [
{ path: '', loadChildren: () => import('./pages/modules/dashboard/dashboard.module').then(m => m.DashboardModule) }
]
},
{
path: '',
component: AuthLayoutComponent,
children: [
{ path: 'session', loadChildren: () => import('./pages/modules/session/session.module').then(m => m.SessionModule) }
]
},
{
path: '**',
redirectTo: 'session/not-found'
}];
And this is my AuthGuard. It localestorage doesn't have a session, then redirects to the login page.
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router, public authService: AuthService) {}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (localStorage.getItem('session')) {
// logged in so return true
return true;
}
// not logged in so redirect to login page with the return url
this.router.navigate(['session/signin']);
return false;
}
}
this is kind of what I want to do whit the canLoad in AuthModuleGuard, but this doesn't work
public canLoad(): Observable<boolean> {
return this.store.select('session').pipe(
take(1),
map((authstate) => {
console.log('Token status', authstate.token);
if (authstate.token !== null) {
return true;
} else {
this.router.navigate(['session/signin']);
return false;
}
})
);
}
if I do this... the application gives me errors and still loads both files
{
path: '',
component: AppLayoutComponent,
canLoad: [ AuthModuleGuard ],
children: [ ... ]
}
and If I do this ... the app never finish loading
{ path: '', canLoad: [ AuthModuleGuard ], loadChildren: () => import('./pages/modules/dashboard/dashboard.module').then(m => m.DashboardModule) },
HERE IS A STACKBLITZ EXAMPLE (including my folder structure)---> https://stackblitz.com/edit/angular-ivy-nasx7r
I need a way to load the dashboard module (and other modules) only if the token in my store are seted, and If is not, redirect to the Login. Please help
After spending some time on this, I've learnt some very interesting things:
loadChildren
and children
in your route configuration, the latter will be chosenif (route.children) {
// The children belong to the same module
return of(new LoadedRouterConfig(route.children, ngModule));
}
if (route.loadChildren) { /* ... */ }
This also denotes that canLoad
is redundant in this case:
{
path: '',
component: AppLayoutComponent,
canLoad: [ AuthModuleGuard ],
children: [ ... ]
}
as this route guard has effect when used together with loadChildren
.
you should be mindful of when to redirect from your guard
With a configuration like this:
{
path: '',
component: AppLayoutComponent,
children: [
{
path: '',
loadChildren: () => import('./pages/modules/dashboard/dashboard.module').then(m => m.DashboardModule),
canLoad: [AuthModuleGuard]
}
]
},
and a canLoad
guard like this:
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean> {
return this.store.select('session').pipe(
take(1),
map((authstate) => {
console.log('Token status', authstate.token);
if (authstate.token !== null) {
return true;
} else {
this.router.navigate(['session/signin']);
return false;
}
})
);
}
you'll get into an infinite loop. When the app first loads, it will go through each configuration in a depth-first manner and will compare the path with the current segments(initially, segments = []
).
But remember that if a route has a children
property, it will go through each of them and see if the segments match the route. Since the child route has path: ''
, it will match any segments and because it has loadChildren
, it will invoke the canLoad
guards.
Eventually, this lines will be reached:
this.router.navigate(['session/signin']);
return false;
this.router.navigate(['session/signin']);
indicates a redirect, which means it will repeat the steps delineated above.
The solution I came up with is to add a pathMatch: 'full'
to your child route:
{
path: '',
component: AppLayoutComponent,
children: [
{
path: '',
pathMatch: 'full',
loadChildren: () => import('./pages/modules/dashboard/dashboard.module').then(m => m.DashboardModule),
canLoad: [AuthModuleGuard]
}
]
},
When the app loads, the segments will be an empty array and because path: ''
matches any group of segments and that group of segments is []
initially, there will be a match:
if (route.path === '') {
if ((route.pathMatch === 'full') && (segmentGroup.hasChildren() || segments.length > 0)) {
return {matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {}};
}
return {matched: true, consumedSegments: [], lastChild: 0, positionalParamSegments: {}};
}
This means that the guard will be invoked and the if
's alternative block will be reached and this.router.navigate(['session/signin'])
will be invoked.
Next time the comparison is made, the segments will be (roughly) ['session', 'signin']
and there will be no match, since this is returned:
{matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {}}
If no match occurs, it will keep on searching until something is found, but the guard will not be invoked again.