Angular路由守卫与导航控制
Angular路由守卫基础
在Angular应用中,路由守卫是一种强大的机制,用于控制应用内的导航行为。它们在导航发生前、发生后或者在特定条件下决定是否允许导航继续进行。这对于保护应用的敏感区域、确保用户具备适当的权限以及管理应用的状态非常重要。
路由守卫的类型
- CanActivate CanActivate守卫用于决定是否可以激活一个路由。这通常用于验证用户是否有权限访问特定的路由。例如,只有登录用户才能访问用户个人资料页面。 代码示例:
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
if (this.authService.isLoggedIn()) {
return true;
} else {
this.router.navigate(['/login']);
return false;
}
}
}
在上述代码中,AuthGuard
实现了CanActivate
接口。canActivate
方法检查用户是否已登录(通过AuthService
的isLoggedIn
方法)。如果用户已登录,返回true
允许导航;否则,导航到登录页面并返回false
。
- CanActivateChild
CanActivateChild守卫与CanActivate类似,但它专门用于保护子路由。当一个父路由被激活时,它的所有子路由也会检查
CanActivateChild
守卫。 代码示例:
import { Injectable } from '@angular/core';
import { CanActivateChild, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class ChildAuthGuard implements CanActivateChild {
constructor(private authService: AuthService, private router: Router) {}
canActivateChild(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
if (this.authService.hasChildAccess()) {
return true;
} else {
this.router.navigate(['/no-access']);
return false;
}
}
}
假设AuthService
中的hasChildAccess
方法用于判断用户是否有权限访问子路由。若有权限则返回true
,否则导航到无访问权限页面并返回false
。
- CanDeactivate CanDeactivate守卫用于在离开当前路由之前进行确认。这在用户可能有未保存的更改时非常有用,例如在编辑表单未保存时提示用户。 代码示例:
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { EditComponent } from './edit.component';
@Injectable({
providedIn: 'root'
})
export class CanDeactivateGuard implements CanDeactivate<EditComponent> {
canDeactivate(component: EditComponent): boolean {
if (component.hasUnsavedChanges()) {
return window.confirm('你有未保存的更改,确定离开吗?');
}
return true;
}
}
在EditComponent
中,hasUnsavedChanges
方法用于判断是否有未保存的更改。如果有,弹出确认框询问用户是否离开,根据用户的选择返回相应的布尔值。
- CanLoad CanLoad守卫用于在加载一个惰性加载的模块之前决定是否允许加载。这对于在模块级别进行权限控制非常有用,例如只有特定用户角色才能加载某些功能模块。 代码示例:
import { Injectable } from '@angular/core';
import { CanLoad, Route, UrlSegment, Router } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class FeatureModuleGuard implements CanLoad {
constructor(private authService: AuthService, private router: Router) {}
canLoad(route: Route, segments: UrlSegment[]): boolean {
if (this.authService.canLoadFeature()) {
return true;
} else {
this.router.navigate(['/forbidden']);
return false;
}
}
}
这里AuthService
的canLoadFeature
方法判断用户是否有权限加载该功能模块。有权限则返回true
,否则导航到禁止访问页面并返回false
。
配置路由守卫
在Angular应用中,配置路由守卫非常直观。在路由配置数组中,可以为每个路由或模块指定相应的守卫。
在路由配置中使用CanActivate
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { ProfileComponent } from './profile.component';
import { AuthGuard } from './auth.guard';
const routes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: 'profile', component: ProfileComponent, canActivate: [AuthGuard] }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
在上述代码中,profile
路由配置了AuthGuard
,这意味着只有当AuthGuard
的canActivate
方法返回true
时,才能导航到ProfileComponent
。
在路由配置中使用CanActivateChild
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ParentComponent } from './parent.component';
import { Child1Component } from './child1.component';
import { Child2Component } from './child2.component';
import { ChildAuthGuard } from './child - auth.guard';
const routes: Routes = [
{
path: 'parent', component: ParentComponent,
children: [
{ path: 'child1', component: Child1Component },
{ path: 'child2', component: Child2Component, canActivateChild: [ChildAuthGuard] }
]
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
这里child2
子路由配置了ChildAuthGuard
,当尝试激活child2
路由时,会检查ChildAuthGuard
的canActivateChild
方法。
在路由配置中使用CanDeactivate
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { EditComponent } from './edit.component';
import { CanDeactivateGuard } from './can - deactivate.guard';
const routes: Routes = [
{ path: 'edit', component: EditComponent, canDeactivate: [CanDeactivateGuard] }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
当尝试离开edit
路由(即EditComponent
所在路由)时,会调用CanDeactivateGuard
的canDeactivate
方法进行确认。
在路由配置中使用CanLoad
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { FeatureModuleGuard } from './feature - module.guard';
const routes: Routes = [
{
path: 'feature',
loadChildren: () => import('./feature.module').then(m => m.FeatureModule),
canLoad: [FeatureModuleGuard]
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
对于惰性加载的FeatureModule
,只有当FeatureModuleGuard
的canLoad
方法返回true
时,才会加载该模块。
高级路由守卫应用
多守卫组合
在实际应用中,一个路由可能需要多个守卫来满足复杂的业务需求。例如,一个管理页面可能既需要用户登录,又需要特定的用户角色权限。
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdminComponent } from './admin.component';
import { AuthGuard } from './auth.guard';
import { RoleGuard } from './role.guard';
const routes: Routes = [
{ path: 'admin', component: AdminComponent, canActivate: [AuthGuard, RoleGuard] }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
在上述代码中,admin
路由配置了AuthGuard
和RoleGuard
。只有当AuthGuard
的canActivate
方法返回true
(用户已登录)且RoleGuard
的canActivate
方法也返回true
(用户具有相应角色)时,才能导航到AdminComponent
。
动态路由守卫
有时候,路由守卫的逻辑可能需要根据路由参数或其他动态条件进行调整。例如,根据不同的用户ID,判断用户是否有权限访问特定资源。
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { UserService } from './user.service';
@Injectable({
providedIn: 'root'
})
export class DynamicAuthGuard implements CanActivate {
constructor(private userService: UserService, private router: Router) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
const userId = next.params['userId'];
if (this.userService.hasAccess(userId)) {
return true;
} else {
this.router.navigate(['/access - denied']);
return false;
}
}
}
在路由配置中:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { UserDetailsComponent } from './user - details.component';
import { DynamicAuthGuard } from './dynamic - auth.guard';
const routes: Routes = [
{ path: 'user/:userId', component: UserDetailsComponent, canActivate: [DynamicAuthGuard] }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
这里DynamicAuthGuard
根据路由参数userId
判断用户是否有访问权限。
基于服务的路由守卫逻辑复用
为了提高代码的可维护性和复用性,可以将路由守卫的核心逻辑封装在服务中,然后在不同的守卫中复用这些逻辑。
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AccessService {
checkAccess(): boolean {
// 实际的访问检查逻辑,例如检查用户角色、权限等
return true;
}
}
然后在路由守卫中使用该服务:
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AccessService } from './access.service';
@Injectable({
providedIn: 'root'
})
export class MyAccessGuard implements CanActivate {
constructor(private accessService: AccessService, private router: Router) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
if (this.accessService.checkAccess()) {
return true;
} else {
this.router.navigate(['/restricted']);
return false;
}
}
}
这样,如果访问检查逻辑发生变化,只需要在AccessService
中修改,所有依赖该服务的路由守卫都会自动应用新的逻辑。
导航控制与路由守卫的交互
导航控制方法
在Angular中,Router
类提供了多种导航控制方法,例如navigate
、navigateByUrl
等。这些方法与路由守卫密切相关,因为它们触发导航行为,而路由守卫会在导航过程中进行检查。
- navigate方法
navigate
方法用于以相对或绝对路径的方式导航到指定路由。
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app - my - component',
templateUrl: './my - component.html'
})
export class MyComponent {
constructor(private router: Router) {}
goToProfile() {
this.router.navigate(['/profile']);
}
}
当调用goToProfile
方法时,会触发导航到/profile
路由。如果/profile
路由配置了CanActivate
守卫,那么在导航之前会检查该守卫。
- navigateByUrl方法
navigateByUrl
方法允许通过完整的URL字符串进行导航。
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app - my - component',
templateUrl: './my - component.html'
})
export class MyComponent {
constructor(private router: Router) {}
goToCustomUrl() {
this.router.navigateByUrl('/home/sub - page');
}
}
同样,在导航之前会检查目标路由的相关守卫。
路由守卫对导航控制的影响
- 阻止导航
当路由守卫的相关方法(如
canActivate
、canDeactivate
等)返回false
时,导航将被阻止。例如,在AuthGuard
中,如果用户未登录,canActivate
返回false
,导航到需要登录权限的路由将被阻止,并根据守卫中的逻辑进行相应处理(如导航到登录页面)。 - 条件导航
路由守卫可以根据不同的条件决定导航的方向。例如,在一个多角色应用中,
RoleGuard
可以根据用户角色决定导航到不同的管理页面。
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { UserService } from './user.service';
@Injectable({
providedIn: 'root'
})
export class RoleGuard implements CanActivate {
constructor(private userService: UserService, private router: Router) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
const role = this.userService.getRole();
if (role === 'admin') {
this.router.navigate(['/admin - dashboard']);
return true;
} else if (role === 'editor') {
this.router.navigate(['/editor - dashboard']);
return true;
} else {
this.router.navigate(['/no - access']);
return false;
}
}
}
在上述代码中,RoleGuard
根据用户角色进行不同的导航,只有符合条件的角色才能导航到相应的页面,否则导航到无访问权限页面。
路由守卫与应用状态管理
基于路由守卫更新应用状态
路由守卫可以在导航过程中更新应用的状态。例如,当用户登录成功并通过AuthGuard
导航到受保护路由时,可以更新应用的用户登录状态。
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';
import { Store } from '@ngrx/store';
import { setLoggedIn } from './store/actions/user.actions';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router, private store: Store<any>) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
if (this.authService.isLoggedIn()) {
this.store.dispatch(setLoggedIn());
return true;
} else {
this.router.navigate(['/login']);
return false;
}
}
}
这里假设使用NgRx进行状态管理,当用户通过AuthGuard
时,会派发setLoggedIn
action来更新应用的用户登录状态。
应用状态对路由守卫的影响
应用的当前状态也会影响路由守卫的决策。例如,在一个多步骤向导应用中,当前步骤的状态决定了是否可以导航到下一步。
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { WizardService } from './wizard.service';
@Injectable({
providedIn: 'root'
})
export class StepGuard implements CanActivate {
constructor(private wizardService: WizardService, private router: Router) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
const currentStep = this.wizardService.getCurrentStep();
const nextStep = parseInt(next.params['step'], 10);
if (nextStep === currentStep + 1) {
return true;
} else {
this.router.navigate(['/wizard', currentStep]);
return false;
}
}
}
在上述代码中,StepGuard
根据当前向导步骤和目标步骤决定是否允许导航。如果目标步骤不是当前步骤的下一步,则导航回当前步骤并阻止导航。
常见问题与解决方法
守卫不生效
- 原因
- 守卫未正确配置在路由中。例如,可能在路由配置数组中拼写错误守卫的名称,或者没有将守卫添加到正确的路由属性(如
canActivate
、canDeactivate
等)中。 - 守卫服务没有正确注入。如果守卫是一个依赖注入的服务,确保它在根模块或相关模块中正确提供,并且注入令牌与使用的名称一致。
- 守卫未正确配置在路由中。例如,可能在路由配置数组中拼写错误守卫的名称,或者没有将守卫添加到正确的路由属性(如
- 解决方法
- 仔细检查路由配置,确保守卫名称正确且配置在合适的位置。例如:
const routes: Routes = [
{ path: 'protected - route', component: ProtectedComponent, canActivate: [AuthGuard] }
];
- 确认守卫服务的提供方式。如果使用`providedIn: 'root'`,确保没有在其他模块中重复提供导致冲突。如果手动在模块中提供,确保模块导入和提供的正确性。
@NgModule({
providers: [AuthGuard]
})
export class AppModule {}
多个守卫执行顺序问题
- 原因 当一个路由配置了多个守卫时,它们的执行顺序可能会影响应用的逻辑。如果不了解执行顺序,可能会导致预期外的结果。例如,一个用于检查登录状态的守卫和一个用于检查用户角色的守卫,执行顺序不当可能会使角色检查在未登录状态下进行,导致错误的判断。
- 解决方法 Angular中,多个守卫的执行顺序是按照它们在路由配置数组中定义的顺序执行的。例如:
const routes: Routes = [
{ path: 'admin - route', component: AdminComponent, canActivate: [AuthGuard, RoleGuard] }
];
这里AuthGuard
会先执行,只有当AuthGuard
的canActivate
方法返回true
时,才会执行RoleGuard
的canActivate
方法。因此,在配置多个守卫时,要根据业务逻辑合理安排顺序。
路由守卫与懒加载模块的问题
- 原因
在使用懒加载模块时,可能会遇到路由守卫配置不正确导致模块无法加载或加载后导航异常的问题。例如,可能在懒加载模块的路由配置中遗漏了必要的守卫,或者在模块加载前的
CanLoad
守卫逻辑有误。 - 解决方法 确保懒加载模块的路由配置中正确添加了所需的守卫。例如:
const routes: Routes = [
{
path: 'feature - module',
loadChildren: () => import('./feature - module/feature - module.module').then(m => m.FeatureModuleModule),
canLoad: [FeatureModuleGuard]
}
];
同时,仔细检查CanLoad
守卫的逻辑,确保它能正确判断是否允许加载模块。如果模块加载后导航异常,检查模块内路由的守卫配置是否与应用整体逻辑一致。
通过深入理解和合理运用Angular的路由守卫与导航控制,开发者可以构建更加安全、可靠且用户体验良好的前端应用。无论是简单的权限控制,还是复杂的多条件导航逻辑,路由守卫都提供了强大的工具来实现这些功能。在实际开发中,结合应用的业务需求,灵活运用各种路由守卫类型和导航控制方法,能够有效提升应用的质量和性能。