【Angular】路由

2021/09/09 09:41:56

在根模块中通过 RouterModule.forRoot() 方法来创建单例路由器

在根组件中引入 <router-outlet></router-outlet> 指明路由填充的位置

路由抽离为独立模块

把路由配置写在 pages.routing.module.ts 文件中

此文件作为根路由模块导入到 app.module

RouterModule.forRoot 用来创建全局唯一的根路由实例

  • 根路由模块 pages.routing.module.ts
import { RouterModule } from "@angular/router";
import { NgModule } from "@angular/core";

import { PageAComponent } from "./page-a/page-a.component";
import { PageBComponent } from "./page-b/page-b.component";
import { PageCComponent } from "./page-c/page-c.component";

@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: "a",
        component: PageAComponent,
        children: [
          {
            path: "c",
            component: PageCComponent,
          },
        ],
      },
      {
        path: "b",
        component: PageBComponent,
      },
      {
        path: "**",
        redirectTo: "/a",
        pathMatch: "full",
      },
    ]),
  ],
})
export class PagesRoutingModule {}

异步路由(懒加载)及导入其他子路由模块

每一个功能模块都有自己的 module.ts 文件 及 routing.ts 文件,把每个功能模块的路由及依赖都各自管理,最终只需要在上级模块中引入这个功能模块的 module.ts 即可

  • 根路由模块 pages/pages.routing.module.ts
import { RouterModule } from "@angular/router";
import { NgModule } from "@angular/core";
import { HomeComponent } from "./home/home.component";

@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: "home",
        component: HomeComponent, // home是一个普通组件,内部有 <router-outlet></router-outlet> 标签做嵌套路由
        children: [
          {
            path: "",
            loadChildren: () =>
              import("./page-module-a/page-module-a.module").then(
                (m) => m.PageModuleAModule
              ), // 懒加载路由
          },
        ],
      },
      {
        path: "**",
        redirectTo: "/home", // 所有未定义的路由都跳转到home
        pathMatch: "full",
      },
    ]),
  ],
})
export class PagesRoutingModule {}
  • 子功能模块 pages/page-module-a/page-module-a.module.ts
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { PageBComponent } from "./page-b/page-b.component";
import { PageAComponent } from "./page-a/page-a.component";
import { PageModuleARoutingModule } from "./page-module-a.routing.module";

@NgModule({
  declarations: [PageBComponent, PageAComponent],
  imports: [CommonModule, PageModuleARoutingModule],
})
export class PageModuleAModule {}
  • 子路由模块 pages/page-module-a/page-module-a.routing.module.ts
import { NgModule } from "@angular/core";
import { PageBComponent } from "./page-b/page-b.component";
import { PageAComponent } from "./page-a/page-a.component";
import { RouterModule, Routes } from "@angular/router";

const routes: Routes = [
  {
    path: "module-a-page-a",
    component: PageAComponent,
  },
  {
    path: "module-a-page-b",
    component: PageBComponent,
  },
];

@NgModule({
  declarations: [],
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class PageModuleARoutingModule {}

带参路由

path 设置为 args-page/:id 来定义一个带参路由, id 对于这个路由来说是必须的

在带参路由指定的组件中通过 ActivatedRoute 来获取路由参数

  • 路由模块 pages/pages-routing.module.ts
import { RouterModule } from "@angular/router";
import { NgModule } from "@angular/core";
import { ArgsPageComponent } from "./args-page/args-page.component";

@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: "args-page/:id", // id对于此路由来说是必须的,只有形如 args-page/12 这样的url才会匹配
        component: ArgsPageComponent,
      },
    ]),
  ],
})
export class PagesRoutingModule {}
  • 组件 pages/args-page/args-page.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-args-page',
  templateUrl: './args-page.component.html',
  styleUrls: ['./args-page.component.css']
})
export class ArgsPageComponent implements OnInit {
  constructor(
    private route: ActivatedRoute
  ) { }

  id: string;

  ngOnInit(): void {
    this.route.params.subscribe(params => {
      this.id = params.id;
    })
  }
}

路由守卫

守卫返回一个值,以控制路由器的行为:

  • 如果它返回 true,导航过程会继续

  • 如果它返回 false,导航过程就会终止,且用户留在原地。

  • 如果它返回 UrlTree,则取消当前的导航,并且开始导航到返回的这个 UrlTree

在分层路由的每个级别上,你都可以设置多个守卫。 路由器会先按照从最深的子路由由下往上检查的顺序来检查 CanDeactivate()CanActivateChild() 守卫。 然后它会按照从上到下的顺序检查 CanActivate() 守卫。 如果特性模块是异步加载的,在加载它之前还会检查 CanLoad()守卫。 如果任何一个守卫返回 false,其它尚未完成的守卫会被取消,这样整个导航就被取消了。

类型

可以在一个文件中定义所有守卫,路由只会根据其引用类型执行对应的守卫

  • canActivate 会在任何路由被激活之前触发。

  • canActivateChild 在任何子路由被激活之前触发。注意该守卫需要定义在父路由上守卫其子路由。

  • canDeactivate 离开路由前触发。

  • resolve 在路由激活之前获取路由数据。

    在该方法中异步获取数据,可以返回一个 Promise或一个 Observable 防止组件在获取数据前被渲染,或者直接返回一个值来支持同步方式。

  • canLoad 来处理异步导航到某特性模块的情况。 如果一个异步路由(懒加载)无权访问,即使在其他守卫中返回 false 阻止访问却依然会加载该异步路由的模块,使用 canLoad 可以在无权访问时不加载该模块

执行顺序

  1. 异步路由时执行目标路由的 canLoad
  2. 从上一个路由离开 canDeactivate
  3. 当前子路由守卫 CanActivateChild
  4. 当前路由守卫 canActivate

定义及使用

canDeactivateCanActivateChildcanActivatecanLoad 只需要引入即可

resolve 用于在路由激活之前获取数据,在守卫文件中定义 resolve 方法并返回数据后需要在对应的组件文件中接收数据

  • 路由守卫文件 auth/auth.guard.ts
import { Injectable } from "@angular/core";
import {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  UrlTree,
} from "@angular/router";

@Injectable({
  providedIn: "root",
})
export class AuthGuard implements CanActivate {
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean {
    console.log("AuthGuard", state.url);
    return true;
  }

  canActivateChild(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): any {
    console.log("ChildAuthGuard", state.url);
    return true;
  }

  canDeactivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): any {
    console.log("canDeactivate", state.url);
    return true;
  }

  canLoad() {
    return true;
  }
}
  • 路由模块 pages/pages-routing.module.ts
import { RouterModule } from "@angular/router";
import { NgModule } from "@angular/core";
import { HomeComponent } from "./home/home.component";
import { ArgsPageComponent } from "./args-page/args-page.component";
import { AuthGuard } from "../auth/auth.guard";

@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: "args-page/:id",
        component: ArgsPageComponent,
        canActivate: [AuthGuard], // 跳转当前路由时触发
        canDeactivate: [AuthGuard], // 离开当前路由时触发
      },
      {
        path: "home",
        component: HomeComponent,
        canActivate: [AuthGuard],
        canActivateChild: [AuthGuard], // 当前子路由变化时触发
        resolve: [AuthGuard], // 接收时数据为数组形式
        // resolve: {               // 接收时数据为对象形式
        //     heroData: AuthGuard
        // },
        children: [
          {
            path: "",
            canLoad: [AuthGuard],
            loadChildren: () =>
              import("./page-module-a/page-module-a.module").then(
                (m) => m.PageModuleAModule
              ),
          },
        ],
      },
      {
        path: "**",
        redirectTo: "/home", // 所有未定义的路由都跳转到home
        pathMatch: "full",
      },
    ]),
  ],
})
export class PagesRoutingModule {}
  • 接收 resolve 返回的数据,pages/home/home.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';

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

  constructor(
    private route: ActivatedRoute
  ) { }

  ngOnInit(): void {
    this.route.data.subscribe(data => {
      console.log('home---data', data);     // 在定义路由时如果resolve守卫使用的是数组形式则data为数组,用对象形式则为对象。
    })
  }
}

预加载

只要有 canLoad 守卫就不会被预加载

自定义预加载策略

这里配置的策略为:data.preloadtrue 时预加载

  • 路由模块 pages/pages-routing.module.ts
import { RouterModule } from "@angular/router";
import { NgModule } from "@angular/core";
import { HomeComponent } from "./home/home.component";
import { AuthGuard } from "../auth/auth.guard";
import { SelectivePreloadingStrategyService } from "./selective-preloading-strategy.service";

@NgModule({
  imports: [
    RouterModule.forRoot(
      [
        {
          path: "home",
          component: HomeComponent,
          children: [
            {
              path: "",
              // canLoad: [AuthGuard],   // 只要有canLoad守卫就不会被预加载
              loadChildren: () =>
                import("./page-module-a/page-module-a.module").then(
                  (m) => m.PageModuleAModule
                ),
              data: {
                preload: true, // data中的数据可以被守卫获取到
              },
            },
          ],
        },
      ],
      {
        preloadingStrategy: SelectivePreloadingStrategyService,
      }
    ),
  ],
})
export class PagesRoutingModule {}
  • 预加载服务 pages/selective-preloading-strategy.service.ts
import { Injectable } from "@angular/core";
import { PreloadingStrategy, Route } from "@angular/router";
import { Observable, of } from "rxjs";

@Injectable({
  providedIn: "root",
})
export class SelectivePreloadingStrategyService implements PreloadingStrategy {
  preloadedModules: string[] = [];
  preload(route: Route, load: () => Observable<any>): Observable<any> {
    if (route.data && route.data["preload"]) {
      this.preloadedModules.push(route.path);
      return load();
    } else {
      return of(null);
    }
  }
}