Chapter 04

路由与导航

构建多页面 SPA:懒加载、路由守卫、动态参数与大型应用路由架构

1. 路由基础配置

Angular 路由基于 Routes 数组配置,每条路由规则将 URL 路径映射到一个组件。配置完后通过 provideRouter(routes) 注册到应用。

// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './pages/home.component';
import { NotFoundComponent } from './pages/not-found.component';

export const routes: Routes = [
  // 基础路由
  { path: '', component: HomeComponent },           // 首页
  { path: 'about', component: AboutComponent },      // 关于页

  // 带参数的路由
  { path: 'users/:id', component: UserDetailComponent },

  // 懒加载(Standalone 组件)
  {
    path: 'dashboard',
    loadComponent: () =>
      import('./pages/dashboard.component')
        .then(m => m.DashboardComponent)
  },

  // 懒加载(路由组)
  {
    path: 'admin',
    loadChildren: () =>
      import('./admin/admin.routes')
        .then(m => m.adminRoutes)
  },

  // 重定向
  { path: 'home', redirectTo: '', pathMatch: 'full' },

  // 通配符(404 页面,放最后)
  { path: '**', component: NotFoundComponent },
];

在模板中使用路由

<!-- app.component.html -->
<nav>
  <!-- routerLink 指令 -->
  <a routerLink="/">首页</a>
  <a routerLink="/dashboard">仪表盘</a>
  <a [routerLink]="['/users', userId]">用户详情</a>

  <!-- routerLinkActive:激活时添加 CSS 类 -->
  <a routerLink="/about" routerLinkActive="active">关于</a>
</nav>

<!-- 路由出口:匹配的组件渲染在此处 -->
<router-outlet />

2. 路由懒加载

懒加载(Lazy Loading)是大型应用必须采用的优化策略:只在用户访问对应路由时才下载相关代码,减少初始加载体积。

loadComponent:懒加载单个组件

// 懒加载单个 Standalone 组件(Angular 14+)
{
  path: 'profile',
  loadComponent: () =>
    import('./profile/profile.component')
      .then(c => c.ProfileComponent)
}

loadChildren:懒加载路由组

// admin.routes.ts — 管理后台的子路由
import { Routes } from '@angular/router';

export const adminRoutes: Routes = [
  { path: '', redirectTo: 'users', pathMatch: 'full' },
  {
    path: 'users',
    loadComponent: () =>
      import('./users/user-list.component')
        .then(c => c.UserListComponent)
  },
  {
    path: 'settings',
    loadComponent: () =>
      import('./settings/settings.component')
        .then(c => c.SettingsComponent)
  },
];

// app.routes.ts 中引用
{
  path: 'admin',
  canActivate: [authGuard],  // 路由守卫
  loadChildren: () =>
    import('./admin/admin.routes')
      .then(m => m.adminRoutes)
}

3. 路由守卫

路由守卫控制是否允许激活或离开某条路由,常用于权限验证、未保存表单提示等场景。Angular 14+ 推荐使用函数式守卫(替代类守卫)。

CanActivate — 访问前验证

// auth.guard.ts — 函数式路由守卫(现代写法)
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';

export const authGuard: CanActivateFn = (route, state) => {
  const auth = inject(AuthService);
  const router = inject(Router);

  if (auth.isLoggedIn()) {
    return true;
  }

  // 未登录:跳转到登录页,并记录目标 URL
  return router.createUrlTree(
    ['/login'],
    { queryParams: { returnUrl: state.url } }
  );
};

// 角色守卫
export const adminGuard: CanActivateFn = () => {
  const auth = inject(AuthService);
  return auth.user()?.role === 'admin';
};

CanDeactivate — 离开前确认

import { CanDeactivateFn } from '@angular/router';

export interface CanComponentDeactivate {
  canDeactivate(): boolean;
}

export const unsavedChangesGuard: CanDeactivateFn<CanComponentDeactivate> =
  (component) => {
    if (component.canDeactivate()) {
      return true;
    }
    return confirm('有未保存的修改,确定要离开吗?');
  };

// 在路由中使用
{
  path: 'edit/:id',
  component: EditComponent,
  canDeactivate: [unsavedChangesGuard]
}

4. 路由参数与查询参数

import { Component, inject, OnInit, signal } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-user-detail',
  standalone: true,
  template: `
    @if (user()) {
      <h1>{{ user()!.name }}</h1>
    } @else {
      <p>加载中...</p>
    }
  `
})
export class UserDetailComponent implements OnInit {
  private route = inject(ActivatedRoute);
  private router = inject(Router);

  // 读取路由参数(/users/:id)
  userId = toSignal(this.route.params, { initialValue: {} });

  // 读取查询参数(?tab=settings)
  queryParams = toSignal(this.route.queryParams, { initialValue: {} });

  user = signal<User | null>(null);

  ngOnInit() {
    const id = this.route.snapshot.paramMap.get('id');
    // 使用 id 加载用户数据...
  }

  goBack() {
    this.router.navigate(['/users']);
  }

  switchTab(tab: string) {
    this.router.navigate([], {
      queryParams: { tab },
      queryParamsHandling: 'merge'  // 合并现有查询参数
    });
  }
}

5. 子路由

// 带子路由的配置
{
  path: 'users',
  component: UsersComponent,  // 布局组件(含 <router-outlet>)
  children: [
    { path: '', component: UserListComponent },
    { path: ':id', component: UserDetailComponent },
    { path: ':id/edit', component: UserEditComponent },
  ]
}

<!-- users.component.html — 布局组件 -->
<div class="users-layout">
  <aside class="sidebar">
    <!-- 侧边导航 -->
  </aside>
  <main>
    <router-outlet />  <!-- 子路由渲染在此 -->
  </main>
</div>

6. 实战:企业应用路由架构

// app.routes.ts — 完整企业应用路由示例
export const routes: Routes = [
  // 公开路由
  { path: 'login', loadComponent: () => import('./auth/login.component').then(c => c.LoginComponent) },
  { path: 'register', loadComponent: () => import('./auth/register.component').then(c => c.RegisterComponent) },

  // 需要认证的路由(使用布局组件包裹)
  {
    path: '',
    component: MainLayoutComponent,
    canActivate: [authGuard],
    children: [
      { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
      { path: 'dashboard', loadComponent: () => import('./dashboard/dashboard.component').then(c => c.DashboardComponent) },
      {
        path: 'users',
        loadChildren: () => import('./users/users.routes').then(m => m.userRoutes)
      },
      {
        path: 'admin',
        canActivate: [adminGuard],
        loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes)
      },
    ]
  },

  { path: '**', loadComponent: () => import('./pages/not-found.component').then(c => c.NotFoundComponent) },
];

本章小结:Angular 路由是构建 SPA 的关键。懒加载 loadComponent/loadChildren 减少初始包体积,函数式路由守卫实现权限控制,子路由实现布局嵌套。下一章深入 HTTP 数据请求。