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

本文详细介绍了Angular中的路由功能,包括基本页面布局、手动创建路由模块、使用`routerLink`和`ActivatedRoute`进行导航、动态路径处理以及编程式导航。作者还强调了如何订阅`ActivatedRoute`以实现数据的动态更新,以及防止内存泄漏的注意事项。
摘要由CSDN通过智能技术生成

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


之前部分 Angular 笔记:


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

基础页面布局

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

在这里插入图片描述

其中:

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

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

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

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

src/app/
├── home
├── servers
│   ├── edit-server
│   └── server
└── users
    └── user

结构如上所示

在没有实现路由功能的时候,可以结合前面学习的案例,采用 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 {}

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

@NgModule({
  declarations: [
    // ...
  ],
  imports: [BrowserModule, FormsModule, AppRoutingModule],
})
export class AppModule {}

将路由单独拆分成一个 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>

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

使用 route

渲染对应 router

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

<ul class="nav nav-tabs">
  <li
    role="presentation"
    routerLinkActive="active"
    [routerLinkActiveOptions]="{ exact: true }"
  >
    <a routerLink="/">Home</a>
  </li>
  <li role="presentation" routerLinkActive="active">
    <a [routerLink]="['/servers']">Servers</a>
  </li>
  <li role="presentation" routerLinkActive="active">
    <a [routerLink]="'/users'">Users</a>
  </li>
</ul>

<router-outlet></router-outlet>

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

  • 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>

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',
    });
  }
}

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>

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

在这里插入图片描述

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

const appRoutes: Routes = [
  // 其余不变
  {
    path: 'servers/:id',
    component: ServerComponent,
  },
];

其中 :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)
    );
  }
}

其中 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>

<!-- <button class="btn btn-primary" (click)="onEdit()">Edit Server</button> -->

<div class="">
  <a [routerLink]="['/servers', 2]">Click me to server 2</a>
</div>

主要是新增加了一个超链接,然后完成重定向到 /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));
    });
  }
}

实现后效果如下:

在这里插入图片描述

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

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

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

  • 27
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值