Angular同路径''对应不同HomeComponent的路由异常问题
问题分析
Angular路由遵循首次匹配优先规则,你的主路由如果先导入了PrivateRoutingModule,访问根路径''时会优先匹配private模块内的路由。未登录状态下,LoginGuard返回false导致该路由无法激活,但Angular不会继续尝试匹配public模块的同路径路由,因此public的HomeComponent无法加载。
解决方案:用
canMatch守卫控制路由匹配(Angular 14+推荐) canMatch守卫用于判断路由是否应该被纳入匹配范围,而非匹配后拦截访问。通过它可以实现:已登录时匹配private路由,未登录时匹配public路由。
1. 创建两个canMatch守卫
LoggedInGuard(仅允许已登录用户匹配private路由)
import { CanMatchFn, Route, UrlSegment } from '@angular/router'; import { inject } from '@angular/core'; import { LoginService } from './login.service'; import { map } from 'rxjs'; export const LoggedInGuard: CanMatchFn = (route: Route, segments: UrlSegment[]) => { const loginService = inject(LoginService); return loginService.isUserLoggedIn().pipe( map(isLoggedIn => { if (isLoggedIn) { console.log('User is logged in'); return true; } // 未登录,跳过private路由,让后续路由尝试匹配 return false; }) ); };
NotLoggedInGuard(仅允许未登录用户匹配public路由)
import { CanMatchFn, Route, UrlSegment } from '@angular/router'; import { inject } from '@angular/core'; import { LoginService } from './login.service'; import { map } from 'rxjs'; export const NotLoggedInGuard: CanMatchFn = (route: Route, segments: UrlSegment[]) => { const loginService = inject(LoginService); return loginService.isUserLoggedIn().pipe( map(isLoggedIn => { if (!isLoggedIn) { console.log('Unknown user'); return true; } // 已登录,跳过public路由 return false; }) ); };
2. 调整主路由(AppRoutingModule)配置
将两个根路径路由放在主路由中,按顺序先检查private路由,再检查public路由:
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { LoggedInGuard } from './logged-in.guard'; import { NotLoggedInGuard } from './not-logged-in.guard'; const routes: Routes = [ // 已登录用户加载private模块 { path: '', loadChildren: () => import('./private/private.module').then(m => m.PrivateModule), canMatch: [LoggedInGuard] }, // 未登录用户加载public模块 { path: '', loadChildren: () => import('./public/public.module').then(m => m.PublicModule), canMatch: [NotLoggedInGuard] }, // 公共路由(登录、注册) { path: 'login', loadChildren: () => import('./public/public.module').then(m => m.PublicModule) }, { path: 'register', loadChildren: () => import('./public/public.module').then(m => m.PublicModule) }, // 404重定向 { path: '**', redirectTo: '' } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
3. 简化子模块路由配置
private-routing.module.ts(移除原canActivate守卫)
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HomeComponent } from './components/home/home.component'; const routes: Routes = [ { path: '', component: HomeComponent, data: { isPrivate: true }, title: "Home" } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class PrivateRoutingModule { }
public-routing.module.ts(保持基础配置)
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HomeComponent } from './components/home/home.component'; import { LoginComponent } from './components/login/login.component'; import { RegisterComponent } from './components/register/register.component'; const routes: Routes = [ { path: '', component: HomeComponent, data: { isPrivate: false }, title: "Home" }, { path: 'login', component: LoginComponent }, { path: 'register', component: RegisterComponent } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class PublicRoutingModule { }
4. 优化原LoginGuard逻辑
保留拦截已登录用户访问登录/注册页的功能,去掉根路径的拦截逻辑:
@Injectable() export class PermissionService { constructor(private loginService: LoginService, private router: Router) {} canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { return this.loginService.isUserLoggedIn().pipe( map((isLoggedIn: boolean): boolean => { if (['login', 'register'].includes(next.routeConfig?.path || '')) { if (isLoggedIn) { // 已登录用户访问登录/注册页,重定向到首页 this.router.navigate(['/']).then(() => window.scrollTo(0, 0)); return false; } return true; } return true; }) ); } } export const LoginGuard: CanActivateFn = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> => { return inject(PermissionService).canActivate(next, state); }
之后给登录、注册路由添加canActivate: [LoginGuard]即可。
验证效果
- 已登录状态:访问
''时,LoggedInGuard返回true,加载private模块的HomeComponent - 未登录状态:访问
''时,LoggedInGuard返回false,Angular自动匹配下一个''路由,NotLoggedInGuard返回true,加载public模块的HomeComponent
内容的提问来源于stack exchange,提问作者xRay
相关产品推荐
相关产品推荐

