• [Angular 基础] - routing 路由(上)


    [Angular 基础] - routing 路由(上)


    之前部分 Angular 笔记:


    终于到 routing 了……这部分的内容比我想象的要复杂很多,果然 Angular 的学习曲线不是开玩笑的 ¯\_(ツ)_/¯

    基础页面布局

    下面是一个简单的 wireframe,在没有实现路由时候的布局:

    在这里插入图片描述

    其中:

    • 第一个模块对应的就是主页,一个非常简单的欢迎信息

    • 第二个模块对应的是服务器管理

      这里的实现是 edit 所属的模块与单独展示的 server 平级

    • 第二个模块对应的是用户信息展示

    src/app/
    ├── home
    ├── servers
    │   ├── edit-server
    │   └── server
    └── users
        └── user
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    结构如上所示

    在没有实现路由功能的时候,可以结合前面学习的案例,采用 ngIf + services 去实现。

    其主要逻辑是:

    • 使用 ngIf 去判断当前应该渲染什么页面

      这个就需要在 app 层添加一个变量去控制当前展示的页面,实现一个 service 去管理对应的点击和更新事件

    • 创建多个组件层级 services

      如一个 service 去管理当前展示的 server,一个 service 去管理当前的 user

    就像之前在案例项目 第一个 Angular 项目 - 添加服务 中实现的那样。不过这样的实现也有一点问题,比如实现会麻烦一些,或者无法根据网址访问对应的资源,如通过 domain/user_id 的方式访问对应的用户,有些验证方式也无法通过,如有些登录验证的方式是通过在 URL 后拼接一些 state id 的方式进行双向验证,这种多为第三方验证验证方式。

    Angular 本身自带路由的实现

    添加路由

    创建一个新的 route module

    这里的创建方式就是手动创建一个 TS 文件,文件名为 app-routing.module.ts,实现方式如下:

    const appRoutes: Routes = [
      { path: '', component: HomeComponent },
      {
        path: 'users',
        component: UsersComponent,
      },
      {
        path: 'servers',
        component: ServersComponent,
      },
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(appRoutes)],
      exports: [RouterModule],
    })
    export class AppRoutingModule {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    随后在 app.module.ts 中导入 AppRoutingModule:

    @NgModule({
      declarations: [
        // ...
      ],
      imports: [BrowserModule, FormsModule, AppRoutingModule],
    })
    export class AppModule {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    将路由单独拆分成一个 module 是为了代码的可读性,以及跟一下 SRP(Single Responsibility Principle),如果不拆分的话,直接将 appRoutes 定义在 app.module.ts 中,并且在 imports 中添加 RouterModule.forRoot(appRoutes) 也可以

    这里代码的分析也比较简单,首先 Route 就是 Angular 定义好的类型:

    在这里插入图片描述

    上面这是 Angular 提供的最简单的配置,需要一个路径,一个组件,这两个是需要的最基础的配置。children 是可选项,代表着子组件(nested component),这里后面会说。

    forRoot() 会创建一个新的,包含所有提供的鹿筋和指令的 ngModule,其语法为:

    static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders<RouterModule>
    
    • 1

    可以看到这是一个静态函数,换言之,这也是一个 singleton

    使用 route

    渲染对应 router

    这里需要更新的是 V 层,抛除一些样式的上的内容,核心部分的代码如下:

    <ul class="nav nav-tabs">
      <li
        role="presentation"
        routerLinkActive="active"
        [routerLinkActiveOptions]="{ exact: true }"
      >
        <a routerLink="/">Homea>
      li>
      <li role="presentation" routerLinkActive="active">
        <a [routerLink]="['/servers']">Serversa>
      li>
      <li role="presentation" routerLinkActive="active">
        <a [routerLink]="'/users'">Usersa>
      li>
    ul>
    
    <router-outlet>router-outlet>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这里总共有这么几个需要注意的点:

    • routerLinkActive

      routerLinkActive 也是一个指令,它会动态的添加指定的类名,当前情况下这个类名就是 active,展示效果如下:

      在这里插入图片描述

      可以看到,随着 nav link 的变动,Angular 也会自动修改对应的类名——增添或是删除 active

    • routerLinkActiveOptions

      这是比较经常搭配使用 routerLinkActive 的指令,比较常见的选项是 [routerLinkActiveOptions]="{ exact: true }",这样可以保证浏览器的路径和路由提供的 URL 100% 一致时,才会增加对应的 active class

      如果不加的话,所有的路径都会 match / 这个路径,因此就会出现两个 active tabs 的情况:

      在这里插入图片描述

    • routerLink

      routerLink 取代了 href,通过 href 进行定位的方式会导致整个页面重新刷新,从而丢失掉所有的状态——这点和 React 是一样的

      这是配置 path 的方法,我这里一共显示了 3 种写法

      • routerLink="/"

        语法糖缩写,和下一种写法一致,具体在 [Angular 基础] - 自定义指令,深入学习 directive 有提到过

      • [routerLink]="'/users'"

        这是在 path 比较简单的情况下使用,直接提供一个字符串即可

      • [routerLink]="['/servers']"

        这是一个比较常见的用法,主要可以用来比较方便的接受静态数据

        /users/user 为例,

        • [routerLink]="'/users/user'" 会生成一个静态路径,即永远都是 /users/user

          如果想要生成动态路径,那么就需要使用 + 做拼接

        • [routerLink]="['/users', user]" 会生成一个动态路径,如 user 是一个变量名,那么 Angular 就会获取对应的变量,并拼接出对应的路径

          也就是说,生成的路径名可能是 /users/user,也有可能是 /users/user1234

    • router-outlet

      这就是一个 placeholder,当 Angular 完成渲染后,它会动态加载对应的组件

      也就取代之前提到的用 ngIf 渲染的 template

    编程式导航

    这个情况为需要在组件内触发一些事件后进行重定向,如在登陆后重新导航到首页这种重定向操作

    这里的案例为在 Home 页面通过点击事件定向到其他的页面,V 层修改如下:

    <button class="btn btn-primary" (click)="onLoadServers()">
      To Server Page
    button>
    
    • 1
    • 2
    • 3

    VM 层实现:

    export class HomeComponent implements OnInit {
      private servers: { id: number; name: string; status: string }[] = [];
    
      constructor(private router: Router, private route: ActivatedRoute) {}
    
      onLoadServers() {
        this.router.navigate(['servers'], {
          relativeTo: this.route,
          queryParams: { allowEdit: '1' },
          fragment: 'loading',
        });
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    constructor 中的内容通过 dependency injection 实现,这部分具体可以查看 [Angular 基础] - service 服务 这篇笔记,这里不多赘述。这里的 RouterActivatedRoute 都是 Angular 提供用于导航的 service

    其中:

    • Router

      是导航及历史记录的相关服务

      对应的 React Hook 有 useHistory/useNavigate

    • ActivatedRoute

      顾名思义,这是对当前的 active route 进行的封装,可以通过这个 service 轻松获取当前的 path 以及包含的相关数据

      对应的 React Hook 有 useLocation, useParams, useMatch, useLoaderData

    这里的点击事件触发的就是重定向到 servers 这个路径去,注意这里采用的是相对路径,Angular 的路由可以接受绝对路径,也可以接受相对路径,甚至还可以使用 ../ 这样的相对路径。后面的参数则是定向的路由配置:

    • relativeTo: this.route

      这里指的是导航的地址所参考的路径,如当前为 /,那么路径拼接的就是 /servers。如果当前路径是 /servers,那么拼接的路径就是 /servers/servers

      使用相对路径时,一定要使用 relativeTo,因为 Router 不知道当前路径在哪里。当没有接收到 relativeTo 时,Angular 会将所有的路径默认为绝对路径

    • queryParams: { allowEdit: '1' }

      这就是添加 query parameter 的地方

    • fragment: 'loading'

      fragment 为 #some_value,一般用来定向到 HTML 页面中的某一个 id 上去

    定向效果为:

    在这里插入图片描述

    动态接受路径数据

    上面一个 section 提到了相对路径和动态修改路径,这里继续实操一下,修改的是 servers component。

    V 层修改如下

    <div class="row">
      <div class="col-xs-12 col-sm-4">
        <div class="list-group">
          <a
            [routerLink]="['/servers', server.id]"
            [queryParams]="{ allowEdit: server.id === 3 ? '1' : '0' }"
            fragment="loading"
            class="list-group-item"
            *ngFor="let server of servers"
          >
            {{ server.name }}
          a>
        div>
      div>
    div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    VM 层不需要修改就此跳过,这个时候点击路径会发现没有任何的变化:

    在这里插入图片描述

    但是查看 HTML 元素又能发现,router-link 中是有值的。这是因为当前 Angular 的 routing 只针对 /servers 进行了处理,但是并没有对 /servers/id 进行处理,因此这里需要修改一下 app-routing module:

    const appRoutes: Routes = [
      // 其余不变
      {
        path: 'servers/:id',
        component: ServerComponent,
      },
    ];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    其中 :id 代表的是一个动态变量

    这时候就能成功实现重定向:

    在这里插入图片描述

    这个时候的数据显示是不完整的,如果想在在 server component 中获取对应的 server 数据,则需要使用到 ActivatedRoute 这个 service,VM 层修改如下:

    export class ServerComponent implements OnInit {
      server: { id: number; name: string; status: string };
    
      constructor(
        private serversService: ServersService,
        private route: ActivatedRoute,
        private router: Router
      ) {}
    
      ngOnInit() {
        this.server = this.serversService.getServer(
          parseInt(this.route.snapshot.params.id)
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    其中 serversService 只是用来获取当前 server 数据的一个 service,具体实现这里不会提及

    实现后效果如下:

    在这里插入图片描述

    这里可以发现,数据已经可以正常渲染了

    这里需要注意的是这个 snapshot 会获取当前路由的状态,其包含的数据如下:

    在这里插入图片描述

    这里获取的 id 对应的就是 path: 'servers/:id' 中的 :id,也是对 routerLink 中的 ['/servers', server.id],之前的 section 提到过,使用数组传参数,数组中的值可以是字符串,也可以是变量,Angular 会自动拼接变量的值到路由中去。

    同样,这里也可以注意到 navigation 中的 Servers 还是处于 active 的状态,这也是因为没有实现 exact: true,Angular 在匹配字符串的时候,发现当前路径与 /servers 可以匹配,因此还是会添加 active 这一类名到对应的元素上

    动态更新路由数据

    现在更新一下 VM 层,更新如下:

    <h5>{{ server?.name }}h5>
    <p>Server status is {{ server?.status }}p>
    
    
    
    <div class="">
      <a [routerLink]="['/servers', 2]">Click me to server 2a>
    div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    主要是新增加了一个超链接,然后完成重定向到 /servers/2 的实现,效果如下:

    在这里插入图片描述

    可以看到,路径是从 http://localhost:4200/servers/1?allowEdit=0#loading 变成了 http://localhost:4200/servers/2,但是数据却没有任何的更新。

    造成这个的原因是,对于 Angular 来说,当前的页面没有重新渲染——url 仍然是 /servers/:id,因此当前组件不会重新经历一个 销毁 --> 新建 的过程,自然 ngOnInit 并没有重新被触发,数据自然也不会完成对应的更新。

    想要解决这个问题,就需要 subscribe ActivatedRoute 的数据变化,在每次 ActivatedRoute 的数据更新时,也需要更新组件内的数据。

    这里实现如下:

    export class ServerComponent implements OnInit {
      ngOnInit() {
        this.server = this.serversService.getServer(
          parseInt(this.route.snapshot.params.id)
        );
    
        console.log(this.route.snapshot);
    
        this.route.params.subscribe((params: Params) => {
          this.server = this.serversService.getServer(parseInt(params.id));
        });
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    实现后效果如下:

    在这里插入图片描述

    这个实现会在每一次 this.route.params 产生变动时,更新 this.server。另外从实践上来说,这里最好在 ngOnDestroy 里去 unsubscribe 去防止内存泄露,不过因为 ActivatedRoute 是 Angular 提供的 service,Angular 会在组件被销毁的时候自动 unsubscribe。

    如果是自己实现的 service,那就 一定 要做好对应 unsubscribe 的处理

    这里涉及到了 Observable,后面会有专门的部分复习回顾一下 Observable……虽然之前也有笔记写过 rxjs 的 Observable,大概了解过这个的用法

  • 相关阅读:
    使用LiME收集主机物理内存的内容时发生宕机
    adas自动驾驶叉车CAN通讯隔离EMC解决方案
    图论03-【无权无向】-图的深度优先遍历-路径问题/检测环/二分图
    脉冲神经网络原理及应用,脉冲神经网络项目名称
    seleuium 自动测试工具
    【LeetCode】剑指 Offer Ⅱ 第8章:树(12道题) -- Java Version
    【Linux学习笔记】基础命令3
    一个最简verilog代码的分析
    ESP8266-Arduino编程实例-TSL2591数光转换器驱动
    R语言使用epiDisplay包的pyramid函数可视化金字塔图、为金字塔图中的不同性别分组指定不同的颜色
  • 原文地址:https://blog.csdn.net/weixin_42938619/article/details/136428314