canLoad в дочерних маршрутах с использованием NgRx
Я работаю в приложении Angular 9 и пытаюсь загрузить (или нет) конкретный модуль только тогда, когда у моего состояния приложения (ngrx) есть свойство!= Null
Во-первых, у меня на маршрутах есть AuthGuard, но с canActivate. Поэтому я хочу, чтобы модуль 'приборной панели' загружался только тогда, когда у mt AppState есть токен
Вот мой файл маршрута
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'
}];
А это мой AuthGuard. В localestorage нет сеанса, а затем выполняется перенаправление на страницу входа.
@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;
}
}
это то, что я хочу сделать с canLoad в AuthModuleGuard, но это не работает
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;
}
})
);
}
если я это сделаю... приложение выдает ошибки и по-прежнему загружает оба файла
{
path: '',
component: AppLayoutComponent,
canLoad: [ AuthModuleGuard ],
children: [ ... ]
}
и если я сделаю это... приложение никогда не завершит загрузку
{ path: '', canLoad: [ AuthModuleGuard ], loadChildren: () => import('./pages/modules/dashboard/dashboard.module').then(m => m.DashboardModule) },
ЗДЕСЬ ПРИМЕР STACKBLITZ (включая мою структуру папок)---> https://stackblitz.com/edit/angular-ivy-nasx7r
Мне нужен способ загрузить модуль приборной панели (и другие модули), только если токен в моем магазине установлен, а если нет, перенаправить на Login. Пожалуйста помоги
1 ответ
Потратив некоторое время на это, я узнал несколько очень интересных вещей:
- если у вас есть оба
loadChildren
а такжеchildren
в конфигурации вашего маршрута будет выбран последний
if (route.children) {
// The children belong to the same module
return of(new LoadedRouterConfig(route.children, ngModule));
}
if (route.loadChildren) { /* ... */ }
Это также означает, что canLoad
в этом случае избыточно:
{
path: '',
component: AppLayoutComponent,
canLoad: [ AuthModuleGuard ],
children: [ ... ]
}
поскольку эта защита маршрута действует при использовании вместе с loadChildren
.
вы должны помнить о том, когда нужно перенаправить со своего охранника
С такой конфигурацией:
{
path: '',
component: AppLayoutComponent,
children: [
{
path: '',
loadChildren: () => import('./pages/modules/dashboard/dashboard.module').then(m => m.DashboardModule),
canLoad: [AuthModuleGuard]
}
]
},
и canLoad
охранять вот так:
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;
}
})
);
}
вы попадете в бесконечный цикл. Когда приложение загружается впервые, оно проходит через каждую конфигурацию в глубину и сравнивает путь с текущими сегментами (изначальноsegments = []
).
Но помните, что если у маршрута есть children
свойство, он пройдется по каждому из них и проверит, соответствуют ли сегменты маршруту. Поскольку у дочернего маршрутаpath: ''
, он будет соответствовать любым сегментам, и поскольку в немloadChildren
, он вызовет canLoad
охранники.
В конце концов, будут достигнуты следующие строки:
this.router.navigate(['session/signin']);
return false;
this.router.navigate(['session/signin']);
указывает на перенаправление, что означает повторение описанных выше шагов.
Решение, которое я придумал, - добавить pathMatch: 'full'
на ваш дочерний маршрут:
{
path: '',
component: AppLayoutComponent,
children: [
{
path: '',
pathMatch: 'full',
loadChildren: () => import('./pages/modules/dashboard/dashboard.module').then(m => m.DashboardModule),
canLoad: [AuthModuleGuard]
}
]
},
Когда приложение загружается, сегменты будут пустым массивом, и посколькуpath: ''
соответствует любой группе сегментов, и эта группа сегментов []
изначально будет совпадение:
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: {}};
}
Это означает, что будет вызвана охрана и if
альтернативный блок будет достигнут и this.router.navigate(['session/signin'])
будет вызван.
При следующем сравнении сегменты будут (примерно) ['session', 'signin']
и совпадения не будет, так как возвращается:
{matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {}}
Если совпадений не обнаружено, поиск будет продолжаться до тех пор, пока что-то не будет найдено, но охрана больше не будет вызвана.