08 - 路由基础
📖 学习目标
通过本章节学习,您将掌握:
- Angular路由系统基础
- 路由配置和导航
- 路由参数传递
- 路由守卫
- 嵌套路由
- 懒加载模块
🎯 核心概念
1. 什么是路由?
路由是Angular中用于实现单页面应用(SPA)导航的机制,它允许:
- 根据URL显示不同组件
- 实现页面间的导航
- 管理浏览器历史记录
- 支持前进后退按钮
2. 路由系统架构
URL → Router → Route → Component
↓
Browser History
↓
Navigation Events
3. 路由配置
typescript
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'contact', component: ContactComponent },
{ path: '**', component: NotFoundComponent } // 通配符路由
];
🏗️ 基础路由配置
1. 配置路由模块
typescript
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ContactComponent } from './contact/contact.component';
import { NotFoundComponent } from './not-found/not-found.component';
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'contact', component: ContactComponent },
{ path: '**', component: NotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
2. 在根模块中导入
typescript
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule // 导入路由模块
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
3. 在模板中使用路由
html
<!-- app.component.html -->
<nav>
<a routerLink="/home">首页</a>
<a routerLink="/about">关于</a>
<a routerLink="/contact">联系</a>
</nav>
<main>
<router-outlet></router-outlet>
</main>
🔗 路由导航
1. 模板导航
html
<!-- 基本导航 -->
<a routerLink="/home">首页</a>
<a routerLink="/about">关于</a>
<!-- 动态路由 -->
<a [routerLink]="['/user', userId]">用户详情</a>
<!-- 带查询参数 -->
<a [routerLink]="['/search']" [queryParams]="{q: 'angular'}">搜索</a>
<!-- 带片段 -->
<a routerLink="/home" fragment="section1">跳转到章节1</a>
<!-- 激活状态样式 -->
<a routerLink="/home" routerLinkActive="active">首页</a>
2. 编程式导航
typescript
// 在组件中注入Router
import { Component } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-navigation',
template: `
<button (click)="goToHome()">去首页</button>
<button (click)="goToUser(123)">用户详情</button>
<button (click)="goBack()">返回</button>
`
})
export class NavigationComponent {
constructor(
private router: Router,
private route: ActivatedRoute
) {}
goToHome() {
this.router.navigate(['/home']);
}
goToUser(userId: number) {
this.router.navigate(['/user', userId]);
}
goToSearch(query: string) {
this.router.navigate(['/search'], {
queryParams: { q: query }
});
}
goBack() {
this.router.navigate(['../'], { relativeTo: this.route });
}
goForward() {
this.router.navigate(['../'], {
relativeTo: this.route,
queryParams: { page: 2 }
});
}
}
📝 路由参数
1. 路径参数
typescript
// 路由配置
const routes: Routes = [
{ path: 'user/:id', component: UserDetailComponent },
{ path: 'user/:id/edit', component: UserEditComponent }
];
// 获取路径参数
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-user-detail',
template: `
<h2>用户详情</h2>
<p>用户ID: {{ userId }}</p>
<p>用户信息: {{ userInfo | json }}</p>
`
})
export class UserDetailComponent implements OnInit {
userId: string | null = null;
userInfo: any = null;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
// 获取路径参数
this.userId = this.route.snapshot.paramMap.get('id');
// 监听参数变化
this.route.paramMap.subscribe(params => {
this.userId = params.get('id');
this.loadUser();
});
}
private loadUser() {
if (this.userId) {
// 根据ID加载用户信息
// this.userService.getUser(this.userId).subscribe(user => {
// this.userInfo = user;
// });
}
}
}
2. 查询参数
typescript
// 路由配置
const routes: Routes = [
{ path: 'search', component: SearchComponent }
];
// 获取查询参数
@Component({
selector: 'app-search',
template: `
<h2>搜索结果</h2>
<p>搜索关键词: {{ searchQuery }}</p>
<p>页码: {{ currentPage }}</p>
`
})
export class SearchComponent implements OnInit {
searchQuery: string | null = null;
currentPage: number = 1;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
// 获取查询参数
this.searchQuery = this.route.snapshot.queryParamMap.get('q');
this.currentPage = Number(this.route.snapshot.queryParamMap.get('page')) || 1;
// 监听查询参数变化
this.route.queryParamMap.subscribe(params => {
this.searchQuery = params.get('q');
this.currentPage = Number(params.get('page')) || 1;
this.performSearch();
});
}
private performSearch() {
if (this.searchQuery) {
// 执行搜索
console.log(`搜索: ${this.searchQuery}, 页码: ${this.currentPage}`);
}
}
}
3. 路由数据
typescript
// 路由配置
const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
data: {
title: '管理后台',
requiresAuth: true,
roles: ['admin']
}
}
];
// 获取路由数据
@Component({
selector: 'app-admin',
template: `
<h2>{{ pageTitle }}</h2>
<p>需要认证: {{ requiresAuth }}</p>
<p>所需角色: {{ roles.join(', ') }}</p>
`
})
export class AdminComponent implements OnInit {
pageTitle: string = '';
requiresAuth: boolean = false;
roles: string[] = [];
constructor(private route: ActivatedRoute) {}
ngOnInit() {
// 获取路由数据
const data = this.route.snapshot.data;
this.pageTitle = data['title'] || '';
this.requiresAuth = data['requiresAuth'] || false;
this.roles = data['roles'] || [];
}
}
🛡️ 路由守卫
1. CanActivate守卫
typescript
// auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean {
if (this.authService.isAuthenticated()) {
return true;
} else {
this.router.navigate(['/login'], {
queryParams: { returnUrl: state.url }
});
return false;
}
}
}
// 在路由中使用
const routes: Routes = [
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [AuthGuard]
}
];
2. CanDeactivate守卫
typescript
// unsaved-changes.guard.ts
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { Observable } from 'rxjs';
export interface CanComponentDeactivate {
canDeactivate(): Observable<boolean> | Promise<boolean> | boolean;
}
@Injectable({
providedIn: 'root'
})
export class UnsavedChangesGuard implements CanDeactivate<CanComponentDeactivate> {
canDeactivate(
component: CanComponentDeactivate
): Observable<boolean> | Promise<boolean> | boolean {
return component.canDeactivate ? component.canDeactivate() : true;
}
}
// 在组件中实现
@Component({...})
export class UserEditComponent implements CanComponentDeactivate {
hasUnsavedChanges = false;
canDeactivate(): boolean {
if (this.hasUnsavedChanges) {
return confirm('您有未保存的更改,确定要离开吗?');
}
return true;
}
}
3. Resolve守卫
typescript
// user-resolver.service.ts
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { UserService } from './user.service';
@Injectable({
providedIn: 'root'
})
export class UserResolver implements Resolve<any> {
constructor(private userService: UserService) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<any> {
const userId = route.paramMap.get('id');
return this.userService.getUser(userId!);
}
}
// 在路由中使用
const routes: Routes = [
{
path: 'user/:id',
component: UserDetailComponent,
resolve: { user: UserResolver }
}
];
// 在组件中获取解析的数据
@Component({...})
export class UserDetailComponent implements OnInit {
user: any;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.user = this.route.snapshot.data['user'];
}
}
🎮 实践练习
练习1:创建完整的导航系统
创建一个包含以下功能的路由系统:
- 首页、关于、联系页面
- 用户列表和详情页面
- 登录和注册页面
- 404错误页面
练习2:实现路由守卫
为应用添加:
- 认证守卫
- 角色权限守卫
- 未保存更改守卫
- 数据预加载守卫
📚 详细示例
完整的路由配置
typescript
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from './guards/auth.guard';
import { RoleGuard } from './guards/role.guard';
import { UnsavedChangesGuard } from './guards/unsaved-changes.guard';
const routes: Routes = [
// 首页
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
// 认证相关
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent },
// 用户相关
{
path: 'users',
component: UserListComponent,
canActivate: [AuthGuard]
},
{
path: 'user/:id',
component: UserDetailComponent,
canActivate: [AuthGuard],
resolve: { user: UserResolver }
},
{
path: 'user/:id/edit',
component: UserEditComponent,
canActivate: [AuthGuard],
canDeactivate: [UnsavedChangesGuard]
},
// 管理相关
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard, RoleGuard],
data: { roles: ['admin'] }
},
// 关于和联系
{ path: 'about', component: AboutComponent },
{ path: 'contact', component: ContactComponent },
// 懒加载模块
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
canActivate: [AuthGuard]
},
// 404页面
{ path: '**', component: NotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
enableTracing: false, // 开发时启用路由跟踪
scrollPositionRestoration: 'top' // 页面切换时滚动到顶部
})],
exports: [RouterModule]
})
export class AppRoutingModule { }
导航组件
typescript
// navigation.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { filter } from 'rxjs/operators';
@Component({
selector: 'app-navigation',
template: `
<nav class="navbar">
<div class="nav-brand">
<a routerLink="/home">MyApp</a>
</div>
<div class="nav-links">
<a routerLink="/home"
routerLinkActive="active"
[routerLinkActiveOptions]="{exact: true}">
首页
</a>
<a routerLink="/about" routerLinkActive="active">关于</a>
<a routerLink="/contact" routerLinkActive="active">联系</a>
<!-- 认证用户可见 -->
<ng-container *ngIf="isAuthenticated">
<a routerLink="/users" routerLinkActive="active">用户</a>
<a routerLink="/dashboard" routerLinkActive="active">仪表板</a>
</ng-container>
<!-- 管理员可见 -->
<a *ngIf="isAdmin" routerLink="/admin" routerLinkActive="active">管理</a>
</div>
<div class="nav-auth">
<ng-container *ngIf="!isAuthenticated; else userMenu">
<a routerLink="/login">登录</a>
<a routerLink="/register">注册</a>
</ng-container>
<ng-template #userMenu>
<div class="user-menu">
<span>欢迎,{{ currentUser?.name }}</span>
<button (click)="logout()">退出</button>
</div>
</ng-template>
</div>
</nav>
`,
styleUrls: ['./navigation.component.css']
})
export class NavigationComponent implements OnInit {
isAuthenticated = false;
isAdmin = false;
currentUser: any = null;
currentUrl = '';
constructor(
private router: Router,
private authService: AuthService
) {}
ngOnInit() {
// 监听路由变化
this.router.events
.pipe(filter(event => event instanceof NavigationEnd))
.subscribe((event: NavigationEnd) => {
this.currentUrl = event.url;
});
// 监听认证状态变化
this.authService.authState$.subscribe(user => {
this.isAuthenticated = !!user;
this.currentUser = user;
this.isAdmin = user?.role === 'admin';
});
}
logout() {
this.authService.logout();
this.router.navigate(['/home']);
}
}
面包屑组件
typescript
// breadcrumb.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
import { filter, distinctUntilChanged } from 'rxjs/operators';
interface BreadcrumbItem {
label: string;
url: string;
}
@Component({
selector: 'app-breadcrumb',
template: `
<nav class="breadcrumb">
<ol>
<li *ngFor="let item of breadcrumbs; let last = last">
<a *ngIf="!last" [routerLink]="item.url">{{ item.label }}</a>
<span *ngIf="last" class="current">{{ item.label }}</span>
</li>
</ol>
</nav>
`,
styleUrls: ['./breadcrumb.component.css']
})
export class BreadcrumbComponent implements OnInit {
breadcrumbs: BreadcrumbItem[] = [];
constructor(
private router: Router,
private activatedRoute: ActivatedRoute
) {}
ngOnInit() {
this.router.events
.pipe(
filter(event => event instanceof NavigationEnd),
distinctUntilChanged()
)
.subscribe(() => {
this.breadcrumbs = this.buildBreadcrumbs(this.activatedRoute.root);
});
}
private buildBreadcrumbs(route: ActivatedRoute, url: string = '', breadcrumbs: BreadcrumbItem[] = []): BreadcrumbItem[] {
const children: ActivatedRoute[] = route.children;
if (children.length === 0) {
return breadcrumbs;
}
for (const child of children) {
const routeURL: string = child.snapshot.url.map(segment => segment.path).join('/');
if (routeURL !== '') {
url += `/${routeURL}`;
}
const label = child.snapshot.data['breadcrumb'];
if (label) {
breadcrumbs.push({ label, url });
}
return this.buildBreadcrumbs(child, url, breadcrumbs);
}
return breadcrumbs;
}
}
🔧 高级路由功能
1. 懒加载模块
typescript
// 路由配置
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule)
},
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
}
];
// 预加载策略
@NgModule({
imports: [RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules // 预加载所有模块
})],
exports: [RouterModule]
})
export class AppRoutingModule { }
2. 自定义预加载策略
typescript
// custom-preloading-strategy.ts
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class CustomPreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
// 只预加载标记为preload的路由
if (route.data && route.data['preload']) {
return load();
}
return of(null);
}
}
// 在路由中使用
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
data: { preload: true }
}
];
✅ 学习检查
完成本章节后,请确认您能够:
- [ ] 配置基本路由
- [ ] 实现路由导航
- [ ] 处理路由参数
- [ ] 使用路由守卫
- [ ] 实现嵌套路由
- [ ] 配置懒加载
🚀 下一步
完成本章节学习后,请继续学习09-路由高级功能。