在 Angular 应用程序的开发过程中,页面导航的控制和保护是一个非常重要的需求。用户常常需要在不同的页面之间切换,而我们必须确保用户在执行某些操作之前已经满足特定的条件,例如是否登录、是否有权限、是否保存了当前的表单数据等等。为了实现这些需求,Angular 提供了一些机制,最常见的就是 PageGuard

PageGuard 是一种技术手段,它通常是通过 CanActivateCanDeactivate 或其他导航守卫来实现的。这些守卫会在用户尝试进入或离开某个页面时进行逻辑判断,从而决定是否允许该操作发生。在这个过程中,PageGuard 扮演了至关重要的角色,它帮助我们增强了应用的健壮性与安全性。

接下来,将从以下几个方面深入分析 PageGuard 的相关知识:其定义、应用场景、工作原理以及如何在 Angular 应用中实现和使用它。

PageGuard 的定义与作用

在 Angular 中,页面守卫(Guard)是通过实现 CanActivateCanActivateChildCanDeactivateResolve 等接口来控制路由导航的组件。守卫会在用户尝试访问一个路由前或路由离开时执行一段逻辑,判断是否允许该导航。

PageGuard 的核心作用是控制页面的进入和退出。通过这种控制,开发者可以:

  • 确保用户登录后才能访问某些页面。
  • 在用户离开页面之前,提示用户保存未保存的更改。
  • 验证用户是否有权限访问特定的页面或资源。
  • 处理异步数据加载,例如在路由激活前预先加载数据。

这使得 PageGuard 在构建复杂的 Angular 应用时,尤其是涉及用户认证、权限验证和数据加载等功能时,显得尤为重要。

使用场景

在实际应用中,PageGuard 可以用于以下几种常见场景:

  1. 用户身份验证:只有当用户登录后,才能访问某些敏感页面。例如,如果用户未登录且试图访问需要权限的页面,可以通过 PageGuard 阻止该操作并将用户重定向到登录页面。

  2. 页面跳转确认:当用户在表单页做了修改但没有保存时,尝试离开该页面时,PageGuard 可以提示用户是否保存更改或放弃更改,以防数据丢失。

  3. 权限验证:对于一些需要特定权限的页面,PageGuard 可以根据用户角色或权限来判断是否允许访问。例如,只有管理员角色的用户才能访问管理后台。

  4. 异步数据加载:有时,在页面访问前需要加载一些数据。可以通过 Resolve 守卫实现这一功能,在数据加载完成之前,页面不会被激活。

  5. 路由生命周期控制:可以在不同的路由阶段(如激活前、激活后、离开前)添加逻辑,进行必要的控制。

PageGuard 的实现原理

Angular 中的守卫机制是通过实现接口来实现的,守卫本质上是一些服务,它们在特定的时机(如路由激活、路由离开等)进行调用。以下是一些常见的守卫类型:

  • CanActivate:控制路由是否可以被激活。在路由激活之前,Angular 会调用实现了该接口的守卫方法。
  • CanDeactivate:控制路由是否可以被离开。当用户尝试离开当前路由时,Angular 会调用实现该接口的守卫方法。
  • CanActivateChild:用于控制子路由的激活情况,类似于 CanActivate,但只对子路由生效。
  • Resolve:在路由激活之前执行异步操作,预加载数据,确保数据加载完成后再激活路由。

所有这些守卫都通过返回一个 Observable<boolean>Promise<boolean> 来控制导航。如果返回 true 或者 Observable 中的值为 true,则允许导航;如果返回 falseObservable 中的值为 false,则阻止导航。

实现一个简单的 PageGuard

接下来,我们通过实际代码来实现一个简单的 PageGuard,并解释每一步的实现细节。以下是一个常见的场景:在用户离开页面之前,提示他们是否保存未保存的更改。

1. 创建 UnsavedChangesGuard

我们创建一个新的守卫,UnsavedChangesGuard,用于检查是否有未保存的更改。如果有更改,我们会提示用户是否保存。

// unsaved-changes.guard.ts
import { Injectable } from `@angular/core`;
import { CanDeactivate } from `@angular/router`;

export interface CanComponentDeactivate {
  canDeactivate: () => boolean | Promise<boolean>;
}

@Injectable({
  providedIn: `root`
})
export class UnsavedChangesGuard implements CanDeactivate<CanComponentDeactivate> {
  canDeactivate(component: CanComponentDeactivate): boolean | Promise<boolean> {
    return component.canDeactivate ? component.canDeactivate() : true;
  }
}

在上面的代码中,我们实现了 CanDeactivate 接口。CanDeactivate 接口要求我们实现一个 canDeactivate 方法,它会在用户尝试离开当前页面时调用。如果返回 true,则允许离开,如果返回 false,则阻止离开。

2. 创建一个页面组件

接下来,我们创建一个页面组件,它实现了 CanComponentDeactivate 接口,并提供一个 canDeactivate 方法。在该方法中,我们模拟了一个有未保存更改的场景,如果用户没有保存更改,返回 false,否则返回 true

// my-form.component.ts
import { Component } from `@angular/core`;
import { CanComponentDeactivate } from `./unsaved-changes.guard`;

@Component({
  selector: `app-my-form`,
  template: `
    <h2>My Form</h2>
    <form (ngSubmit)="save()">
      <input [(ngModel)]="formData" name="formData" />
      <button type="submit">Save</button>
    </form>
  `
})
export class MyFormComponent implements CanComponentDeactivate {
  formData = ``;
  saved = false;

  canDeactivate(): boolean {
    if (!this.saved && this.formData) {
      return window.confirm(`You have unsaved changes. Do you really want to leave?`);
    }
    return true;
  }

  save(): void {
    this.saved = true;
    alert(`Form saved`);
  }
}

在该组件中,canDeactivate 方法检查是否有未保存的更改。如果有未保存的更改,调用 window.confirm 弹出确认框,询问用户是否确定离开。

3. 在路由中配置守卫

我们需要在路由配置中为该页面添加 UnsavedChangesGuard,以便在用户尝试离开该页面时触发该守卫。

// app-routing.module.ts
import { NgModule } from `@angular/core`;
import { RouterModule, Routes } from `@angular/router`;
import { MyFormComponent } from `./my-form/my-form.component`;
import { UnsavedChangesGuard } from `./unsaved-changes.guard`;

const routes: Routes = [
  {
    path: `form`,
    component: MyFormComponent,
    canDeactivate: [`UnsavedChangesGuard`]
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

在上面的路由配置中,canDeactivate 被设置为 UnsavedChangesGuard,这意味着当用户试图离开 MyFormComponent 时,UnsavedChangesGuardcanDeactivate 方法将会被调用。

总结

在 Angular 中,PageGuard 是通过路由守卫来实现的,可以有效地控制页面的进入和离开。常见的守卫有 CanActivateCanDeactivateCanActivateChildResolve,它们各自处理不同的导航阶段。通过实现这些守卫,开发者可以确保用户的操作符合预期,避免数据丢失、权限问题或其他潜在的应用漏洞。