使用 NgRx 在子路由中加载 canLoad

canLoad in children routes using NgRx

我在 Angular 9 应用程序中工作,并且仅当我的应用程序状态 (ngrx) 具有 属性 != null 时,我才尝试加载(或不加载)特定模块

首先,我的路线中有一个 AuthGuard,但带有 canActivate。 所以我希望 'dashboard' 模块仅在 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。它的本地存储没有会话,然后重定向到登录页面。

@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;
  }
}

这就是我想用 AuthModuleGuard 中的 canLoad 做的事情,但这不起作用

  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

我需要一种方法来加载仪表板模块(和其他模块),仅当我商店中的令牌已设置时,如果未设置,则重定向到登录。请帮忙

在这方面花了一些时间之后,我学到了一些非常有趣的东西:

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;
          }
      })
  );
}

您将进入无限循环。当应用程序首次加载时,它将以 深度优先 的方式遍历每个配置,并将 path 与当前段进行比较(最初, 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: '' 匹配任何段组并且该组段最初是 []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: {}};
}

这意味着将调用守卫并且将到达 if 的替代块并调用 this.router.navigate(['session/signin'])

下次进行比较时,段将(大致)为 ['session', 'signin'] 并且不会有匹配项,因为返回的是:

{matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {}}

如果没有匹配出现,它将继续搜索直到找到某些东西,但不会再次调用守卫。

StackBlitz