angular - 路由的示例


1 创建路由#

创建路由配置 - RouterModule#

添加导航链接#

@NgModule({
  imports: [BrowserModule, FormsModule, ModelModule, RouterModule], //导入 RouterModul

使用

<button
        class="btn btn-warning btn-sm"
        (click)="editProduct(item.id)"
        routerLink="/form/edit"
      >

完成路由实现#

  • ActivatedRoute:主要用于在组件中实时获取和处理路由相关的动态信息,重点在于与组件交互,响应路由参数等的变化情况,方便组件根据路由状态来更新自身的展示内容或执行相关逻辑。
  • ActivatedRouteSnapshot:侧重于获取路由在某一特定时刻的静态信息,常用于路由守卫等场景,在不需要关注路由后续变化,仅对当前路由初始状态进行判断或操作时使用。
  • ActivationStart 和 ActivationEnd:它们是路由激活过程的起始和结束事件标记,更多地是从路由生命周期的角度出发,辅助开发者在路由激活这个阶段的开始和结束时刻执行一些额外的操作,比如加载提示的显示与隐藏等,并不直接涉及路由具体参数等信息的获取与处理。

组件中处理路由 - ActivatedRoute#

snapshot: 返回一个 ActivatedRouteSnapshot 对象,描述当前路由

分类 名称 描述
方法 params 返回一个可观察对象(Observable),用于订阅路由中的路径参数变化情况。例如,当路由路径中的参数值改变时(如从 /user/1 变为 /user/2,这里 1 和 2 是路径参数),可以通过订阅该可观察对象获取最新的参数值,并在组件中相应地更新逻辑或数据展示,示例:this.route.params.subscribe(params => { this.userId = params['userId']; });
方法 queryParams 同样返回一个可观察对象,用于订阅路由中的查询参数(通过 ? 及后面的键值对形式添加在 URL 后的参数,如 /user?id=123 中的 id 就是查询参数)的变化情况,方便组件根据查询参数的改变来执行相应操作,比如重新加载数据等,示例:this.route.queryParams.subscribe(queryParams => { this.filter = queryParams['filter']; });
方法 fragment 也是返回可观察对象,用于订阅 URL 中的片段标识符(即 # 后面的部分)的变化情况,不过相对来说使用场景没有前两者那么普遍,例如:this.route.fragment.subscribe(fragment => { console.log(fragment); });
方法 data 返回一个可观察对象,用于获取从路由配置中关联到当前路由的静态数据(在路由配置对象里通过 data 字段设置的数据),可用于向组件传递额外的信息,比如页面标题等,示例:this.route.data.subscribe(data => { this.pageTitle = data['title']; });
方法 parent 返回父级 ActivatedRoute 的实例,用于在嵌套路由场景下,访问父路由相关的信息,例如获取父路由的参数、数据等内容,有助于构建更复杂的、具有层级关系的路由逻辑,示例:const parentRoute = this.route.parent;
方法 children 返回一个包含当前路由所有子路由对应的 ActivatedRoute 实例的数组,在处理多层嵌套路由,且需要操作子路由相关信息时使用,比如遍历子路由来执行特定的初始化逻辑等,示例:const childRoutes = this.route.children;
方法 firstChild 返回当前路由的第一个子路由对应的 ActivatedRoute 实例,在只关注首个子路由情况,比如判断是否存在特定的首个子路由或者获取其相关信息时比较方便,示例:const firstChildRoute = this.route.firstChild;
方法 url 返回一个可观察对象,用于获取当前路由的 URL 片段的信息,可了解当前路由路径的具体组成情况,不过通常在更复杂的自定义路由处理场景中使用较多,示例:this.route.url.subscribe(url => { console.log(url); });
属性 routeConfig 只读属性,提供当前激活路由对应的路由配置对象(也就是在路由配置文件里定义该路由的那个对象),从中可以获取路由的路径、对应的组件、是否有子路由等基础配置信息,示例:const routeConfig = this.route.routeConfig;
属性 snapshot 只读属性,返回 ActivatedRouteSnapshot 类型的对象,它代表了当前路由在某一时刻的一个 “快照”,包含了当时路由的路径参数、查询参数、数据等静态信息,常用于在不需要响应路由后续变化,仅获取当前激活路由初始状态的情况,例如在路由守卫中判断权限等操作,示例:const snapshot = this.route.snapshot;

form.component.ts

export class FormComponent {
  product: Product = new Product();

  constructor(
    private model: Model,
    private state: SharedState,
    activatedRoute: ActivatedRoute
  ) {
    // 组件中使用 route,使用 ActivatedRoute
    // 因为如果是url 是 form/edit,就判断url[1].path
    this.editing = activatedRoute.snapshot.url[1].path == 'edit';
  }

  editing: boolean = false;
  ...

路由参数#

form.component.ts

constructor(
    private model: Model,
    private state: SharedState,
    activatedRoute: ActivatedRoute
  ) {
    // 组件中使用 route,使用 ActivatedRoute
    // 因为如果是url 是 form/edit,就判断url[1].path
    // this.editing = activatedRoute.snapshot.url[1].path == 'edit';
    this.editing = activatedRoute.snapshot.params['mode'] == 'edit';
  }

1 多路由参数:id#

edit 需要传值,form/edit/2,因为多传一个值,配置第2个路由参数:id

{ path: 'form/:mode/:id', component: FormComponent },

如果不配置的话,不能匹配路由

ERROR Error: Uncaught (in promise): Error: NG04002: Cannot match any routes. URL Segment: 'form/edit/2'
Error: NG04002: Cannot match any routes. URL Segment: 'form/edit/2'

传值 使用

[routerLink]="['/form', 'edit', item.id]"

2 可选路由参数;name=Lifejacket#

URL包含附加信息

可选路由用(;)隔开,这个URL包含name, category,price的三个可选信息。

http://localhost:4200/form/edit/2;name=Lifejacket;category=Watersports;price=48.95

传值

[routerLink]="[
          '/form',
          'edit',
          item.id,
          { name: item.name, category: item.category, price: item.price }
        ]"

获取

 constructor(
    private model: Model,
    private state: SharedState,
    activatedRoute: ActivatedRoute
  ) {
    this.editing = activatedRoute.snapshot.params['mode'] == 'edit';
    let id = activatedRoute.snapshot.params['id'];
    if (id != null) {
      let name = activatedRoute.snapshot.params['name'];
      let category = activatedRoute.snapshot.params['category'];
      let price = activatedRoute.snapshot.params['price'];
      if (name != null && category != null && price != null) {
        this.product = new Product(id, name, category, price);
      } else {
        Object.assign(this.product, this.model.getProduct(id) || new Product());
      }
    }
  }

导航 Router#

Router 一个提供导航和操纵 URL 能力的 NgModule

分类 名称 描述
方法 navigate 用于进行路由导航,接收一个路由路径数组(可包含路径参数等)以及可选的导航配置对象,以此切换页面,例如:this.router.navigate(['/home']);
方法 navigateByUrl 基于给定的完整 UrlTree 或者字符串形式的 URL 来执行导航操作,如:this.router.navigateByUrl('/user/profile');,直接依据完整 URL 跳转。
方法 events 返回一个可观察对象(Observable),能订阅路由相关的各种事件,像导航开始、导航结束、路由激活、路由结束等事件,方便开发者按需处理不同阶段的逻辑,示例:this.router.events.subscribe(event => { /* 处理事件逻辑 */ });
方法 parseUrl 把一个字符串形式的 URL 解析成 UrlTree 结构,便于后续更深入地处理路由路径相关信息,常用于自定义复杂路由逻辑构建场景。
方法 serializeUrl 与 parseUrl 相反,将 UrlTree 结构转变成字符串形式的 URL,适用于把路由对象转换为展示或传递用的 URL 字符串的情况。
方法 createUrlTree 依据给定的命令式路由片段(如路径、查询参数等)创建 UrlTree,可灵活构建符合需求的路由树结构,常用于动态生成路由路径场景。
方法 resetConfig 用于重置整个路由配置,接收新的路由配置对象,在需动态改变应用路由规则时使用,比如按用户权限动态调整可用路由路径等情况。
属性 url 只读属性,返回当前激活路由的 URL 字符串,可用于获取当前页面所在路由路径情况,例如在组件中通过 console.log(this.router.url); 查看当前 URL。
属性 config 只读属性,呈现当前应用的全部路由配置信息,以路由配置对象数组形式展现,开发者可查看已定义的所有路由路径、对应组件及相关路由策略等内容。
属性 routerState 只读属性,涵盖当前路由的状态信息,包含当前激活的路由快照(ActivatedRouteSnapshot)等内容,便于深入了解当前路由详细情况及层级关系等。

1 代码中导航 navigateByUrl#

constructor(
    private model: Model,
    private state: SharedState,
    activatedRoute: ActivatedRoute,
    private router: Router
  ){...}
  ...

    submitForm(form: NgForm) {
    if (form.valid) {
      this.model.saveProduct(this.product);
      // this.product = new Product();
      // form.reset();
      this.router.navigateByUrl('/');
    }
  }

2 接收导航事件 event#

...

通配符 path: '**' 和重定向 redirectTo, pathMatch#

app.routing.ts

export const routes: Routes = [
  { path: 'form/:mode/:id', component: FormComponent },
  { path: 'form/:mode', component: FormComponent },
  { path: 'does', redirectTo: '/form/create', pathMatch: 'prefix' },//重定向
  { path: 'table', component: TableComponent },
  { path: '', redirectTo: '/table', pathMatch: 'full' },
  { path: '**', component: NotFoundComponent },//通配符
];

以下是Angular路由中prefixfull的解释及对比表格:

属性 prefix full
匹配方式 前缀匹配,只要URL的开始部分与路径匹配即可 完全匹配,URL必须与路径完全一致
应用场景 大多数情况下使用,如常规页面路由匹配等 通常用于空路径重定向等特殊场景,例如将空路径重定向到首页等
子路由匹配 可以继续匹配子路由 无法继续匹配子路由,因为已经匹配了完整路径
示例 路由配置{ path: 'home', component: HomeComponent, pathMatch: 'prefix' },访问/home/123时,会匹配到HomeComponent并继续匹配其子路由 路由配置{ path: '', redirectTo: '/home', pathMatch: 'full' },只有当URL完全为空时,才会重定向到/home

组件内部导航#

1 响应正在发生的路由变化 - ActivatedRoute 返回的 Observable#

repository.model.ts

  getNextProduct(id: number): number {
    let index = this.products.findIndex((p) => this.locator(p, id));
    if (index > -1) {
      return this.products[this.products.length > index + 2 ? index + 1 : 0].id;
    } else {
      return id || 0;
    }
  }

  getPreviousProduct(id: number): number {
    let index = this.products.findIndex((p) => this.locator(p, id));
    if (index > -1) {
      return this.products[index > 0 ? index - 1 : this.products.length - 1].id;
    } else {
      return id || 0;
    }
  }

form.component.html

<div *ngIf="editing" class="p-2">
  <button
    class="btn btn-secondary m-1"
    [routerLink]="['/form', 'edit', model.getPreviousProduct(product.id)]"
  >
    Preview
  </button>
  <button
    class="btn btn-secondary m-1"
    [routerLink]="['/form', 'edit', model.getNextProduct(product.id)]"
  >
    Next
  </button>
</div>

PreviewNext 这时候虽然 URL 会变化,但是数据没变。

因为 使用的是 snapshot(快照) 对象。

记得 ActivatedRoute 返回的 Observable 对象,可以接收通知。

Observable 对象 说明
params 用于获取路由参数的可观察对象。例如,当路由中有像 :id 这样的参数(如 { path: 'user/:id', component: UserComponent }),订阅这个 Observable 就能实时获取到参数值的变化情况,每当路由参数发生改变(比如从 /user/1 切换到 /user/2),就会接收到新的参数值,可以在组件中根据新参数来加载相应的数据,比如获取对应 id 的用户详细信息等。
queryParams 用于获取路由中的查询参数(即 URL 中 ? 后面的参数部分,像 http://example.com/page?param1=value1&param2=value2 里的 param1 和 param2 等)的可观察对象。当查询参数有变化时(比如用户在页面上修改了筛选条件,导致查询参数更新),订阅它就能获取到最新的查询参数值,方便组件据此调整显示内容或者重新发起相应的数据请求等。
fragment 这个 Observable 可获取路由中的片段标识符(也就是 URL 中 # 后面的部分)。在一些单页应用中,可能会利用片段标识符来实现页面内的定位或者触发特定的行为,当片段标识符发生改变时,通过订阅该 Observable 可以捕捉到相应变化并进行对应的操作。
data 通常在路由配置中可以通过 data 属性设置一些自定义的数据(例如 { path: 'dashboard', component: DashboardComponent, data: { title: '管理面板', permission: 'admin' } }),data 这个 Observable 就能获取到这些自定义路由数据,方便组件获取相关配置信息来进行页面标题设置、权限验证等操作,并且在路由切换时若 data 属性有变化,也能及时接收到新的数据。
url 它返回的是一个表示当前路由完整 URL 的可观察对象,每当路由的 URL 发生变化(比如从一个页面导航到另一个页面),订阅该 Observable 可以获取到新的 URL 信息,有助于在组件中进行一些基于 URL 变化的逻辑处理,比如记录用户访问历史等。
parent 是一个指向当前路由的父路由的 Observable。在多级路由嵌套的情况下,组件可以通过订阅这个 Observable 来获取父路由的相关信息,比如父路由的参数、数据等,以便进行一些与父路由协同或者依赖父路由情况的操作,像根据父路由的某个参数来决定子路由组件的显示样式等。
children 用于获取当前路由下的子路由相关信息的 Observable,在动态加载子路由或者需要根据子路由状态来做逻辑处理(比如判断子路由是否全部加载完成等)时很有用,订阅它可以获取到子路由的变化情况及相关详细信息。

form.component.ts

  constructor(
    public model: Model,
    private state: SharedState,
    activatedRoute: ActivatedRoute,
    private router: Router
  ) {
    // 组件中使用 route,使用 ActivatedRoute
    // 因为如果是url 是 form/edit,就判断url[1].path
    // this.editing = activatedRoute.snapshot.url[1].path == 'edit';

    // this.editing = activatedRoute.snapshot.params['mode'] == 'edit';
    // let id = activatedRoute.snapshot.params['id'];
    // if (id != null) {
    //   Object.assign(this.product, this.model.getProduct(id) || new Product());
    // }

    activatedRoute.params.subscribe((params) => {
      this.editing = params['mode'] == 'edit';
      let id = params['id'];
      if (id != null) {
        Object.assign(this.product, this.model.getProduct(id) || new Product());
      }
    });
  }

2 为活动路由设置不同样式的链接 - routerLinkActive#

当 RouterLink 被激活时,还可以通过 RouterLinkActive 指令为其相应的 HTML元素指定 CSS 类。

table.component.html

<div class="container-fluid">
  <div class="row">
    <div class="col-auto">
      <button
        class="btn btn-secondary btn-block"
        routerLink="/"
        routerLinkActive="bg-primary"
      >
        All
      </button>
      <button
        *ngFor="let category of categories"
        class="btn btn-secondary btn-block px-3"
        [routerLink]="['/table', category]"
        routerLinkActive="bg-primary"
      >
        {{ category }}
      </button>
    </div>
    <div class="col">
      <table class="table table-sm table-bordered table-striped">
        ...

table.component.ts

@Component({
  selector: 'paTable',
  templateUrl: 'table.component.html',
})
export class TableComponent {
  constructor(private model: Model, activatedRoute: ActivatedRoute) {
    activatedRoute.params.subscribe((params) => {
      this.category = params['category'] || null;
    });
  }
  category: string = '';

  getProduct(key: number): Product | undefined {
    return this.model.getProduct(key);
  }

  getProducts(): Product[] {
    return this.model
      .getProducts()
      .filter(
        (product) => this.category == null || product.category === this.category
      );
  }

  deleteProduct(key: number) {
    this.model.deleteProduct(key);
  }

  get categories(): string[] {
    return this.model
      .getProducts()
      .map((p) => p.category)
      .filter((c): c is string => typeof c === 'string') // 过滤掉 undefined
      .filter((c, i, a) => a.indexOf(c) === i);
  }

routerLinkActiveOptions 精确匹配 - 修复起始按钮 All

因为 URL"/"默认对活动URL部分匹配,总是导致ALL按钮被激活。

使用 [routerLinkActiveOptions]="{ exact: true }" 来设置为精确匹配,不要忘记 routerLink="/table" 设置为正确的值。

<button
        class="btn btn-secondary btn-block"
        routerLink="/table"
        routerLinkActive="bg-primary"
        [routerLinkActiveOptions]="{ exact: true }"
      >
        All
      </button>

子路由 - children和第2个router-outlet#

创建一些根组件之外的相对路由,这些嵌套路由类型称为子路由。

app.routing.ts

{
    path: 'table',
    component: TableComponent,
    children: [
      { path: 'products', component: ProductCountComponent },
      { path: 'categories', component: CategoryCountComponent },
    ],
  },
  { path: 'table/:category', component: TableComponent },
  { path: 'table', component: TableComponent },

放到table/:category之前,因为它也可以匹配table/products,angular 是从上向下匹配

子路由都显示在一个<router-outlet>,这意味着你要为你的应用添加第二 <router-outlet>,因为它是 table.component.html 之外的另一个 router-outlet

table.component.html

<button class="btn btn-info mx-1" routerLink="/table/products">
        Count Products
      </button>
      <button class="btn btn-primary mx-1" routerLink="/table/categories">
        Count Categories
      </button>
      <button class="btn btn-secondary mx-1" routerLink="/table">
        Count Neither
      </button>
      <div class="my-2">
        <router-outlet></router-outlet>
      </div>

从子路由访问参数 - pathFromRoot#

属性名 类型 描述
parent ActivatedRoute 当前路由的父路由。
firstChild ActivatedRoute 当前路由的第一个子路由。
children ActivatedRoute[] 当前路由的所有子路由。
pathFromRoot ActivatedRoute[] 从根路由到当前路由的路径。

本节针对 /table/:category/products

示例:
http://localhost:4200/table/Watersports/products

http://localhost:4200/table/Soccer/categories

app.routing.ts

const childRoutes: Routes = [
  { path: 'products', component: ProductCountComponent },
  { path: 'categories', component: CategoryCountComponent },
  { path: '', component: ProductCountComponent },
];

export const routes: Routes = [
  { path: 'form/:mode/:id', component: FormComponent },
  { path: 'form/:mode', component: FormComponent },
  { path: 'does', redirectTo: '/form/create', pathMatch: 'prefix' },
  { path: 'table', component: TableComponent, children: childRoutes },
  { path: 'table/:category', component: TableComponent, children: childRoutes },
  { path: '', redirectTo: 'table', pathMatch: 'full' },
  { path: '**', component: NotFoundComponent },
];
t },
];

先改为相对路由。

table.component.html

<button class="btn btn-info mx-1" routerLink="products">
        Count Products
      </button>
      <button class="btn btn-primary mx-1" routerLink="categories">
        Count Categories
      </button>
      <!-- <button class="btn btn-secondary mx-1" routerLink="/table">
        Count Neither
      </button> -->

这2个组件,会收到一个 ActivatedRoute 对象,仅描述选中这些组件的子路由。

productCount.component.ts

@Component({
  selector: 'paProductCount',
  template: `<div class="bg-info text-white p-2">
    There are {{ count }} products
  </div>`,
})
export class ProductCountComponent implements OnInit, DoCheck {
  constructor(
    private model: Model,
    private keyValueDiffers: KeyValueDiffers,
    private changeDetector: ChangeDetectorRef,
    activeRoute: ActivatedRoute
  ) {
    activeRoute.pathFromRoot.forEach((route) => {
      route.params.subscribe((params) => {
        if (params['category'] != null) {
          this.category = params['category'];
          this.updateCount();
        }
      });
    });
  }
  ngOnInit(): void {
    this.differ = this.keyValueDiffers.find(this.model.getProducts()).create();
  }
  private differ?: KeyValueDiffer<any, any>;
  count: number = 0;
  private category: string | null = null;

  ngDoCheck() {
    if (this.differ!.diff(this.model.getProducts()) !== null) {
      this.updateCount();
    }
  }

  private updateCount() {
    this.count = this.model
      .getProducts()
      .filter(
        (p) => this.category == null || p.category === this.category
      ).length;
  }
}

守卫路由#

Angular守卫路由总结

守卫类型 触发时机 描述 返回值
Resolve 路由激活前(异步数据获取) 用于在路由被激活之前异步获取数据,并将数据注入到组件中。这对于需要从服务器获取数据并在组件加载前显示这些数据的情况非常有用。 Observable<T>:返回一个 RxJS 观察者对象。
Promise<T>:返回一个 Promise 对象。
示例要点:实现Resolve接口,重写resolve方法,在其中进行异步数据获取操作。获取的数据将通过路由配置注入到目标组件中。
CanActivate 路由激活前 用于在路由被激活之前进行权限检查或其他逻辑判断。如果返回true,则允许路由激活;如果返回false,则阻止路由激活。常用于保护需要登录或特定权限才能访问的页面。 boolean:直接返回一个布尔值,true 表示可以激活子路由,false 表示不可以激活子路由。
Observable<boolean>:返回一个 RxJS 观察者对象,最终返回一个布尔值。
Promise<boolean>:返回一个 Promise 对象,最终返回一个布尔值。
UrlTree:返回一个 UrlTree 对象,表示重定向到另一个路由。
示例要点:实现CanActivate接口,重写canActivate方法,在其中进行权限检查。
CanActivateChild 子路由激活前 CanActivate类似,但专门用于保护子路由。当尝试访问某个父路由下的子路由时,此守卫会被触发。 boolean
Observable<boolean>
Promise<boolean>
UrlTree
示例要点:实现方式与CanActivate相同,但适用于子路由场景。
CanLoad 懒加载模块加载前 用于在懒加载模块被加载之前进行权限检查或其他逻辑判断。如果返回true,则允许模块加载;如果返回false,则阻止模块加载。 boolean
Observable<boolean>
Promise<boolean>
UrlTree
示例要点:实现CanLoad接口,重写canLoad方法,在其中进行权限检查。注意,与CanActivate不同,CanLoad是在模块加载阶段进行检查,而不是路由激活阶段。
CanDeactivate 离开路由前 用于在离开当前路由之前进行权限检查或其他逻辑判断。如果返回true,则允许离开当前路由;如果返回falseUrlTree(表示重定向到其他路由),则阻止离开当前路由。 boolean
Observable<boolean>
Promise<boolean>
UrlTree
示例要点:实现CanDeactivate接口,重写canDeactivate方法,在其中进行离开前的检查。注意,此方法需要接收一个组件实例作为参数,以便在离开前检查该组件的状态。

守卫模板

请在守卫函数里实现你要用到的守卫。下面的例子使用 canActivate 来保护该路由。

export const yourGuard: CanActivateFn = (
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot) => {
      // your  logic goes here
  }

推迟导航 - Resolve#

1 创建Resolve服务

// articles.resolve.ts
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';
import { ArticleService } from './article.service'; // 假设有一个ArticleService用于获取文章数据

@Injectable({
  providedIn: 'root'
})
export class ArticlesResolve implements Resolve<any[]> {

  constructor(private articleService: ArticleService) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any[]> {
    return this.articleService.getArticles()
      .pipe(
        tap(articles => console.log('Articles resolved:', articles)),
        catchError(error => {
          console.error('Error resolving articles:', error);
          return of([]); // 或者返回某种错误处理数据
        })
      );
  }
}

2 配置路由

// app-routing.module.ts (追加配置)
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ArticlesComponent } from './articles/articles.component';
import { ArticlesResolve } from './articles.resolve';

const routes: Routes = [
  // 已有的路由配置...
  {
    path: 'articles',
    component: ArticlesComponent,
    resolve: {
      articles: ArticlesResolve
    }
  }
];

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

3 在组件中使用解析的数据

// articles.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-articles',
  templateUrl: './articles.component.html',
  styleUrls: ['./articles.component.css']
})
export class ArticlesComponent implements OnInit {
  articles: any[];

  constructor(private route: ActivatedRoute) {}

  ngOnInit(): void {
    this.route.data.subscribe(data => {
      this.articles = data.articles;
    });
  }
}

防止路由激活 - canActivate#

准备工作#

先修改Message 的构造函数

message.model.ts

export class Message {
  constructor(
    public text: string,
    public error: boolean = false,
    public responses?: [string, (string: string) => void][] //[string, (string: string) => void]这个元组的数组
  ) {}
}

message.component.html

<div
  *ngIf="lastMessage"
  class="bg-info text-white p-2 text-center"
  [class.bg-danger]="lastMessage.error"
>
  <h4>{{ lastMessage.text }}</h4>
</div>
<div class="text-center my-2">
  <button
    *ngFor="let resp of lastMessage?.responses; let i = index"
    (click)="resp[1](resp[0])"
    class="btn btn-primary m-2"
    [class.btn-secondary]="i > 0"
  >
    {{ resp[0] }}
  </button>
</div>

开始创建#

1 创建一个canActivate的服务

//terms.gurad.ts
import { Injectable } from '@angular/core';
import { MessageService } from './messages/message.service';
import {
  ActivatedRouteSnapshot,
  Router,
  RouterStateSnapshot,
} from '@angular/router';
import { Message } from './messages/message.model';

@Injectable()
export class TermsGuard {
  constructor(private message: MessageService, private router: Router) {}
    
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Promise<boolean> | boolean {
    if (route.params['mode'] == 'create') {
      return new Promise<boolean>((resolve, reject) => {
        let responses: [string, () => void][] = [
          ['Yes', () => resolve(true)],
          ['No', () => resolve(false)],
        ];
        this.message.reportMessage(
          new Message('Do you accept the terms & conditions?', false, responses)
        );
      });
    }
    return true;
  }
}

2 路由绑定canActivate

export const routes: Routes = [
  {
    path: 'form/:mode/:id',
    component: FormComponent,
    resolve: { module: ModelResolver },
  },
  {
    path: 'form/:mode',
    component: FormComponent,
    canActivate: [TermsGuard],
    resolve: { module: ModelResolver },
  },
  { path: 'table', component: TableComponent, children: childRoutes },
  { path: 'table/:category', component: TableComponent, children: childRoutes },
  { path: '', redirectTo: 'table', pathMatch: 'full' },
  { path: '**', component: NotFoundComponent },
];

防止路由失活 - canDeactivate#

常见在未保存被编辑后数据情况,防止用户离开

unsaved.guard.ts

import {
  ActivatedRouteSnapshot,
  Router,
  RouterStateSnapshot,
} from '@angular/router';
import { MessageService } from './messages/message.service';
import { Injectable } from '@angular/core';
import { FormComponent } from './core/form.component';
import { Observable, Subject } from 'rxjs';
import { Message } from './messages/message.model';

@Injectable()
export class UnsavedGuard {
  constructor(private message: MessageService, private router: Router) {}

  canDeactivate(
    component: FormComponent,
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | boolean {
    if (component.editing) {
      if (
        ['name', 'category', 'price'].some(
          (prop: string) =>
            component.product[prop] != component.originalProduct[prop]
        )
      ) {
        let subject = new Subject<boolean>();
        let responses: [string, (string: string) => void][] = [
          [
            'Yes',
            () => {
              subject.next(true);
              subject.complete();
            },
          ],
          [
            'No',
            () => {
              this.router.navigateByUrl(this.router.url);
              subject.next(false);
              subject.complete();
            },
          ],
        ];

        this.message.reportMessage(
          new Message('Discard Changes?', true, responses)
        );
        return subject;
      }
    }
    return true;
  }
}

路由

export const routes: Routes = [
  {
    path: 'form/:mode/:id',
    component: FormComponent,
    resolve: { module: ModelResolver },
    canDeactivate: [UnsavedGuard],
  },
    ...

动态加载 - 懒惰加载(懒加载lazy loading)#

概念

懒加载指的是只有在需要某个模块或者组件时,才去加载相应的代码,而不是在应用启动时就一次性把所有的代码都加载进来。这就好比你去图书馆借书,不会一次性把所有书都搬回家,而是需要哪本借哪本,按需取用,从而减少初始加载时的资源消耗和等待时间。

作用

  • 提升性能:对于大型的 Angular 应用来说,往往包含众多的模块、组件以及相关资源。如果在一开始就全部加载,会导致首次加载时间过长,让用户长时间等待应用启动并变得可用。采用懒加载,初始加载的代码量大幅减少,加快了应用的启动速度,提升了用户体验。
  • 优化资源利用:只加载当前用户操作路径下需要的模块,节省了不必要的网络带宽占用以及浏览器内存占用等资源,尤其对于移动端应用或者网络状况不佳的使用场景来说,这一点十分关键。

实现方式

  • 模块划分:首先需要合理地对应用的功能模块进行划分。例如,一个电商应用可以划分为用户认证模块、商品展示模块、购物车模块、订单管理模块等。每个模块都可以独立地进行懒加载配置。
  • 路由配置:在 Angular 的路由模块中,通过特定的语法来设置懒加载。比如使用loadChildren属性来指定要懒加载的模块路径

需要加载时,才请求

需要加载时,才请求

2种方式

第一种:组件 standalone 为 false ,创建 Module#

第二种:组件 standalone 为 true, 不用 Module#

ondemand.component.ts

@Component({
  standalone: true,
  selector: 'app-ondemand',
  templateUrl: './ondemand.component.html',
  styleUrls: ['./ondemand.component.css'],
})
export class OndemandComponent implements OnInit {
  constructor() {}

  ngOnInit() {}
}

ondemand-Routing.routing.ts

export const routes: Routes = [
  {
    path: '',
    component: OndemandComponent,
  },
];

app.routing.ts

{
    path: 'ondemand',
    loadChildren: () =>
      import('./ondemand/ondemand-Routing.routing').then((m) => m.routes), //这里直接加载 routes
  },

指定命名出口#

Angular 允许一个路由组件包含多个 Outlet,从而可以在一个路由组件中同时显示多个组件。其中,主要 Outlet(Primary Outlet)有且仅有一个,附属 Outlet(Auxiliary Outlet)可以有任意多个,各个附属 Outlet 通过不同的命名加以区分。每一个 Outlet 均可以通过路由配置来指定其可以显示的组件,这使得 Angular 可以灵活地对各个组件进行组合,从而满足不同场景的需求。

ondemand文件夹下新建 first.component.ts , second.component.ts,内容差不多

import { Component } from '@angular/core';

@Component({
  selector: 'first',
  template: `<div class="bg-primary text-white p-2">First Component</div>`,
})
export class FirstComponent {
  constructor() {}
}

ondemand-Routing.routing.ts

export const routes: Routes = [
  {
    path: '',
    component: OndemandComponent,
    children: [
      {
        outlet: 'primary',
        path: '',
        component: FirstComponent,
      },
      {
        outlet: 'left',
        path: '',
        component: SecondComponent,
      },
      {
        outlet: 'right',
        path: '',
        component: SecondComponent,
      },
    ],
  },
  {
    path: 'swap',
    component: OndemandComponent,
    children: [
      {
        outlet: 'primary',
        path: '',
        component: SecondComponent,
      },
      {
        outlet: 'left',
        path: '',
        component: FirstComponent,
      },
      {
        outlet: 'right',
        path: '',
        component: FirstComponent,
      },
    ],
  },
];

ondemand.component.html

<div class="bg-primary text-white p-2">This is the ondemand component</div>

<div class="container-fluid">
  <div class="row">
    <div class="col-12 p-2">
      <!-- 主出口 -->
      <router-outlet></router-outlet>
    </div>
  </div>
  <div class="row">
    <div class="col-6 p-2">
      <router-outlet name="left"></router-outlet>
    </div>
    <div class="col-6 p-2">
      <router-outlet name="right"></router-outlet>
    </div>
  </div>
</div>

<button class="btn btn-secondary m-2" routerLink="/ondemand">Normal</button>
<button class="btn btn-secondary m-2" routerLink="/ondemand/swap">Swap</button>
<button class="btn btn-primary m-2" routerLink="/">Back</button>

作者:【唐】三三

出处:https://www.cnblogs.com/tangge/p/18652484

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   【唐】三三  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
历史上的今天:
2018-01-04 《LINQ技术详解C#》-2.查询表达式翻译为标准查询操作符
2016-01-04 微信支付 - V3退款
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示