三、Angular 路由

news2025/1/10 13:02:27

一、简介

  • Angular 的路由服务是一个可选的服务,它用来呈现指定的 URL 所对应的视图。它并不是Angular 核心库的一部分,而是位于 @angular/router 包中。像其他 Angular 包一样,路由服务在用户需要时才从此包中导入。

[1]. 创建路由模块

  • 默认情况下,用户在使用Angular CLl 命令 ng new 构建 Web 应用程序时系统会提示是否需要路由服务功能,用户可以在命令后添加选项参数 --routing 来指定需要路由服务功能。当选择需要路由服务功能时,Angular CLl 命令将会生成一个独立的路由模块文件,文件名默认为 app-routing.module.ts
  • 在生成的 app-routing.module.ts文件中可以看出,AppRoutingModule 类由 @NgModule() 装饰器声明,说明它是一个NgModule 类,我们称它为 Web 应用程序的路由模块。Web 应用程序的路由模块用于封装路由器配置,它可以在根模块和特性模块级别上使用。
  • Web 应用程序的路由模块具有以下特征:
    • 路由模块不需要 declarations 属性,即不需要声明组件、指令和管道。
    • RouterModule.forRoot(routes) 方法将会注册并返回一个全局的 RouterModule 单例对象imports 元数据导入这个单例对象。
    • exports 元数据导出 RouterModule 单例对象,这里是专门提供给根模块导入的。
    • 路由模块最终由根模块导入。执行 ng new 命令时,Angular 已经帮我们在根模块(src/app/app.module.ts)的imports 元数据中导入了路由模块,这是一个默认选项。
  1. 使用命令创建一个新的带路由的项目

     ng new demo-route --routing
    
  2. src/app 路径下将会生成一个 app-routing.module.ts 文件

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    
    const routes: Routes = [];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
    })
    export class AppRoutingModule { }
    
  3. src/app/app.module.ts 文件中将会引入路由模块

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    
    // 引入路由模块
    import { AppRoutingModule } from './app-routing.module';
    import { AppComponent } from './app.component';
    
    @NgModule({
      declarations: [AppComponent],
      imports: [
        BrowserModule,
        AppRoutingModule, // 导入
      ],
      providers: [],
      bootstrap: [AppComponent],
    })
    export class AppModule {}
    

[2]. 理解路由服务

  • src/appapp-routing.module.ts 文件中可以看到AppRoutingModule 类代码中引用了 Routes 和 RouterModule 对象,它们都是@angular/router 包中导入的系统路由对象。Routes 类用于创建路由配置; RouterModule 也是一个独立的 NgModule 类,用于为用户提供路由服务,这些路由服务包括在 Web 应用程序视图之间进行导航的指令。RouterModule 类中提供了路由服务,该路由服务是全局的一个单例服务,同时还提供了一些路由指令,如 RouterOutlet 和 routerLink 等路由指令。
  • AppRoutingModule 类中导出了 RouterModule 对象,Web 应用程序的根模块中导入了AppRoutingModule 类,即导入了 RouterModule 对象。RouterModule 对象注册了一个全局的路由服务,该路由服务让 Web 应用程序的根组件可以访问各个路由指令。
  • 如果在特性模块中需要使用路由指令,那么需要在特性模块中导入 RouterModule 模块,这样它们的组件模板中才能使用这些路由指令。
  • RouterModule 对象有一个 forChild() 方法,该方法可以传入 Route 对象数组。尽管forChild()forRoot()方法都包含路由指令和配置,但是 forRoot() 方法可以返回路由对象。由于路由服务会改变浏览器的 Location 对象(可以理解为地址栏中的 URL),而Location 对象又是一个全局单例对象,所以路由对象也必须是全局单例对象。这就是在根模块中必须只使用一次forRoot() 方法的原因,特性模块中应当使用 forChild() 方法。
  • 另外需要注意:导入模块的顺序很重要,尤其是路由模块。因为当 Web 应用程序中有多个路由模块时,路由器会接受第一个匹配路径的路由,所以应将 AppRoutingModule 类放置在根模块的 imports 元数据中的最后一项。

二、简单的路由配置

[1]. 基本路由配置

  • Route 对象数组中的每个 Route 对象都会把一个 URL 映射到一个组件。 Route 对象是一个接口类型,它支持静态、参数化、重定向和通配符路由,以及自定义路由数据和解析方法。该接口中的 path 属性用来映射 URL。路由器会先解析 path 属性,然后构建最终的 URL,这样允许用户使用相对或绝对路径在 Web 应用程序的多个视图之间导航。path 属性的值需要满足以下规则。

    • path 属性的值的类型是一个字符串,字符串不能以斜杠“/”开头。
    • path 属性的值可以为空“‘’”,表示Web 应用程序的默认路径,通常是 Web 应用程序的首页地址。
    • path 属性的值可以使用通配符字符串“**”。如果请求的 URL 与定义路由的任何路径都不匹配,则路由器将选择此路由。
    • 如果请求的 URL 找不到匹配项,那么一般要求显示的配置为类似“Not Found”的视图或重定向到特定视图。
    • 路由配置的顺序很重要,路由器仅会接受第一个匹配路径的路由。
    const routes: Routes = [
      // 路由中的空路径“”表示 Web 应用程序的默认路径,当 URL 为空时就会访问。默认路
      //由会重定向到路径“/main”,显示其对应的 DashboardComponent 组件内容
      { path: '', redirectTo: '/main', pathMatch: 'full' },
      // 当URL为“/main'”时,路由将会显示 DashboardComponent 组件的内容
      { path: 'main', component: DashboardCOmponent },
      // 路由中的“**”路径是一个通配符。当所请求的 URL 不配前面定义的任何路径时,路由器就会选择此路由。
      {path: '**', component: PageNotFoundComponent}
    ];
    

[2]. 路由器出口 router-outlet

  • 路由器出口(Router Outlet)是一个来自 RouterModule 模块类的指令,它的语法类似于模板中的插值表达式。它扮演一个占位符的角色,用于在模板中标出一个位置,路由器将会在这个位置显示对应的组件内容。简单地说,前面我们介绍的路由配置中的组件内容都将在这个占位符中显示。路由器出口指令的用法如下。

    <router-outlet></router-outlet>
    
  • 在由 Angular CLI命令 ng new 构建的 Web 应用程序中,可以在根模块中找到路由器出口标签(默认是:src/app/app.component.html)。当完成了路由配置,有了渲染组件的路由器出口后,用户可以在浏览器中输入URL。当 URL满足匹配的路由配置规则时,其对应的组件内容将显示在路由器出口的位置。

  1. 使用命令ng g c first-cpnng g c second-cpn 分别创建组件 first-cpn.component.tssecond-cpn.component.ts,如果是手动创建的这两个文件需要添加到 app.module.ts 文件中

  2. first-cpn.component.ts 文件内容如下

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-first-cpn',
      template: `<p>first 组件</p>
        <button routerLink="../second-cpn">跳到 second 组件</button>`,
    })
    export class FirstCpnComponent {}
    
  3. second-cpn.component.ts 文件内容如下

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-second-cpn',
      template: `<p>second 组件</p>
        <button routerLink="../first-cpn">跳到 first 组件</button>`,
    })
    export class SecondCpnComponent {}
    
  4. app.component.html 文件内容如下

    <router-outlet></router-outlet>
    
  5. app-routing.module.ts 文件中配置路由

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    import { FirstCpnComponent } from './first-cpn/first-cpn.component';
    import { SecondCpnComponent } from './second-cpn/second-cpn.component';
    
    const routes: Routes = [
      {
        path: '',
        redirectTo: '/first-cpn',
        pathMatch: 'full',
      },
      {
        path: 'first-cpn',
        component: FirstCpnComponent,
      },
      {
        path: 'second-cpn',
        component: SecondCpnComponent,
      },
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule],
    })
    export class AppRoutingModule {}
    

[3]. 命名路由器出口(多个路由出口)

  • 在实际应用中,有时会遇到类似这样的问题:在某个页面弹出一个对话框,然后要求在 Web应用程序中的不同页面之间切换时,这个对话框也始终保持打开状态,直到对话框完成任务或者用户手动取消。显然,这个对话框的 URL 在设计上应该是对应不同的路由,而主路由出口在同一时间仅支持一个路由。Angular 提供了命名路由出口来解决类似这样的问题。

  • 命名路由出口相对主路由出口来说,一般称为第二路由。同一个模块视图可以有多个命名路由出口,这些命名路由出口可以在同一时间显示来自不同路由的内容。第二路由就是在路由器出口标签中增加了一个 name 属性,代码如下。

    <router-outlet name="popup"></router-outlet>
    

[4]. 使用路由器链接

  • Angular 中提供了路由器链接指令 routerLink 用于实现相同的导航功能。由于Angular 是单页面应用程序,在 Web 应用程序中不应重新加载页面,因此 routerink 指令导航到新的 URL,在不重新加载页面的情况下,将新组件的内容替换为路由器出口标签。routerLink 指令的简单用法如下。

    <a routerLink="/users">Users</a>
    
  • 当用户单击路由器链接时,路由器会先找到路由配置中的 path 为“/users”的组件,然后将其内容渲染在路由出口标签位置。

  • routerLink 指令还包含以下一些属性。

    • queryParams属性:负责给路由提供查询参数,这些查询参数以键/值对([k: string]:any)的方式出现,跳转过去就类似于/user?id=2。
    • skipLocationChange 属性:内容跳转,路由保持不变。换句话说,就是停留在上一个页面的 URL 而不是新的 URL。
    • fragment属性:负责定位客户端页面的位置,值是一个字符串。以“#”附加在 URL 的末尾,如/second-cpn?debug=true#education
     <a [routerLink]="['/second-cpn']" [queryParams]="{ debug: true }" fragment="education">跳转到secondCpn页面</a>
    
  • 假设有这样的路由配置:[{ path:'user/:name',component: UserComponent}]如果要链接到 user/:name 路由,使用 routerink 指令的具体写法如下。

    • 如果该链接是静态的,可以使用<a routerLink=" /user/bob ">链接到 user 组件 </a>
    • 如果要使用动态值来生成该链接,可以传入一组路径片段,如<a routerLink="[/user,userName] ">链接到 user 组件</a>,其中 userName 是个模板变量。
  • 路径片段也可以包含多组,如[/team',teamld,'user',userName,{details: true}] 表示生成一个到/team/11/user/bob;details=true的链接。这个多组的路径片段可以合并为一组,如['/team/11/user',userName, {details: true}].

[5]. 路由链接的激活状态

  • 单击 routerLink 指令中的链接,意味着当前的路由链接被激活,routerLinkActive 指令会在宿主元素上添加一个 CSS 类。因此 Angular 中的 routerLinkActive 指令一般和 routerLink 指令配合使用,代码如下。

    <a routerLink="/user/bob" routerLinkActive="active">跳转到 bob 组件</a>
    
  • 当 URL 是/user/user/bob时,当前的路由链接为激活状态,active 样式类将会被添加到<a>标签上。如果 URL 发生变化,则 active 样式类将自动从 <a>标签上移除。

  • 默认情况下,路由链接的激活状态会向下级联到路由树中的每个层级,所以父、子路由链接可能会被同时激活。由于上述代码片段中 /user/user/bob 的父路由,因此它们当前的路由链接状态都会被激活。要覆盖这种行为,可以设置 routerLinkActive指令中的 routerLinkActiveOptions属性值为 "{exact: true}",这样只有当 URL 与当前 URL精确匹配时路由链接才会被激活。routerLinkActiveOptions 属性的用法如下

    <a routerlink="/user/bob" routerlinkActive="active" [routerLinkActiveOptions]="{ exact: true }">跳转到 bob 组件</a>
    
  1. 使用命令ng g c first-cpnng g c second-cpnng g c third-cpn 分别创建组件 first-cpn.component.tssecond-cpn.component.tsthird-cpn.component.ts,如果是手动创建的这三个文件需要添加到 app.module.ts 文件中

  2. first-cpn.component.ts 文件内容如下

    import { Component, OnInit } from '@angular/core';
    
    @Component({
      selector: 'app-first-cpn',
      template: ` <p>first</p> `,
    })
    export class FirstCpnComponent {
    }
    
  3. second-cpn.component.ts 文件内容如下,注意:该组件有一个路由出口router-outlet 用于显示其子组件

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-second-cpn',
      template: `<p>second</p>
        <router-outlet></router-outlet>`,
    })
    export class SecondCpnComponent {}
    
  4. third-cpn.component.ts 文件内容如下,注意:该组件将要作为 second-cpn.component.ts 组件的子组件

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-second-cpn',
      template: `<p>second</p>
        <router-outlet></router-outlet>`,
    })
    export class SecondCpnComponent {}
    
  5. 路由模块 src/app/app-routing.module.ts 文件如下

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    
    // 引入三个组件
    import { FirstCpnComponent } from './first-cpn/first-cpn.component';
    import { SecondCpnComponent } from './second-cpn/second-cpn.component';
    import { ThirdCpnComponent } from './third-cpn/third-cpn.component';
    
    const routes: Routes = [
      {
        path: '',
        redirectTo: 'first-cpn',
        pathMatch: 'full',
      },
      {
        path: 'first-cpn',
        component: FirstCpnComponent,
      },
      {
        path: 'second-cpn',
        component: SecondCpnComponent,
        children: [
          {
            path: 'third-cpn', // 作为second-cpn 组件的子组件
            component: ThirdCpnComponent,
          },
        ],
      },
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule],
    })
    export class AppRoutingModule {}
    
  6. src/app/app.component.html 文件如下

    <nav>
        <a routerLink="./first-cpn" routerLinkActive="active" >跳转到 firstCpn 组件</a> | 
        <a routerLink="./second-cpn" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">跳转到 secondCpn 组件</a> |  
        <a [routerLink]="['/second-cpn/third-cpn']" routerLinkActive="active">跳转到 thirdCpn 组件</a> | 
    </nav>
    
    <router-outlet></router-outlet>
    
  7. 点击三个链接,跳转到不同页面, third-cpn 组件将在 second-cpn 组件的路由出口中显示。
    在这里插入图片描述

三、路由状态

  • 由 Angular 开发的 Web 应用程序的页面是由若干个组件视图组成的,当 Web 应用程序在组件之间导航时,路由器使用页面上的路由器出口来呈现这些组件,然后在 URL 中反映所呈现的状态。换句话说,一个URL 将对应若干个可呈现或可视化的组件视图。我们称 Web 应用程序中所有的这些可视化的组件视图及其排列为路由器状态。为此,路由器需要用某种方式将 URL 与要加载的可视化的组件视图相关联。Angular 中定义了一个配置对象来实现此目标,这个配置对象不仅维护着路由器状态,而且描述了给定 URL 呈现哪些组件。

[1]. 路由器状态和激活路由状态

  • 路由器状态在 Angular 中用 RouterState 对象表示,RouterState 对象维护的是一个路由器状态树,表示所有的路由器状态。Angular 中用 ActivatedRoute 对象表示激活路由状态。因此,RouterState对象中包含了 ActivatedRoute 对象。

[2]. ActivatedRoute 对象及其快照对象

  • 每个 ActivatedRoute 对象都提供了从任意激活路由状态开始向上或向下遍历路由器状态树的一种方式,以获得关于父、子、兄弟路由的信息。在 Web 应用程序中,我们可以通过注入 ActivatedRoute 对象来获取当前路由的相关数据信息,ActivatedRoute 对象也可用于遍历路由器状态树。通过 ActivatedRoute 对象获取路由的数据信息的方式主要有两种:一种是通过snapshot属性,获取当前路由的快照对象,快照对象的类型是ActivatedRouteSnapshot类型;另一种是直接通过 params 属性获取,它返回的是一个Observable<Params>对象类型。
  • ActivatedRoute 对象和其快照对象的区别如下。
    • 每当导航添加、删除组件或更新参数时,路由器就会创建一个新的快照对象。
    • 快照对象是一个不变的数据结构,它仅表示路由器在特定时间的状态。在 Web 应用程序中快照对象的表现方式是,该数据结构在组件的生命周期中仅执行一次,如在 ngOninit() 方法中执行一次,代表某一时刻的一个激活路由状态的快照版本。
    • ActivatedRoute 对象类似于快照对象,不同之处在于它表示路由器随时间变化的状态,换句话说,它是一个可观察的数据流对象(Observable 类型 )。因此,在Web 应用程序中需要通过订阅的方式获取其值,进而要求取消订阅(Unsubscrib),甚至要求实现销毁方法。如OnDestroy()
    • ActivatedRoute 对象的 snapshot 属性返回的值是 ActivatedRouteSnapshot类型的快照对象。
    • 在实际应用中,ActivatedRoute 对象可以返回可观察(Observable)对象,只要路由状态发生了变化,订阅在 ActivatedRoute 对象上的方法都会再次执行,直到取消订阅为止。这也是Angular 编程中的核心亮点之一。
    import { Component, OnInit } from '@angular/core';
    import { ActivatedRoute } from '@angular/router';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
    })
    export class AppComponent implements OnInit {
      constructor(private route: ActivatedRoute) {}
    
      ngOnInit() {
        // 快照
        console.log(this.route.snapshot);
        // 订阅
        this.route.data.subscribe((data) => {
          console.log(data);
        });
      }
    }
    
  1. src/app/app.component.html 文件如下

    import { Component } from '@angular/core';
    import { Router, ActivatedRoute } from '@angular/router';
    
    @Component({
      selector: 'app-root',
      template: `<div>{{ title }}</div>
        <button (click)="goto('home')">跳转到 Home</button>
        <button (click)="goto('produce')">跳转到 Produce</button> `,
    })
    export class AppComponent {
      title = '路由用例';
    
      constructor(private router: Router, private route: ActivatedRoute) {
        console.log('该信息只执行一次:title = ' + this.title);
    
        let params = this.route.snapshot.fragment;
        console.log('该信息只执行一次:fragment = ' + params);
    
        // 订阅 ActivatedRoute 对象的 fragement 属性的返回值
        this.route.fragment.subscribe((fragment: string) => {
          console.log('订阅信息:' + fragment);
        });
      }
    
      goto(path) {
        // 调用 Router 对象的 navigate 方法进行导航,设置不同的 # 片段
        this.router.navigate(['/'], { fragment: path });
      }
    }
    
  2. 在浏览器的控制台中的输出如下,注意浏览器 url 的变化
    在这里插入图片描述

四、路由器触发事件(路由器生命周期)

  • 与组件生命周期类似,路由器也有生命周期。在路由的导航周期中,路由器会触发一系列事件。我们可以通过在 RouterModule.forRoot()方法中添加{enableTracing: true} 参数来查看路由器触发的事件

    @NgModule({
      imports: [
        RouterModule.forRoot(routes, {
          enableTracing: true, // 控制台输出所有路由器出发的事件
        }),
      ],
      exports: [RouterModule],
    })
    export class AppRoutingModule {}
    
  • 在浏览器的控制台中可以看到如下信息:

  • 在路由的导航周期中,一些值得注意的事件如下:

    • NavigationStart 事件:表示导航周期的开始。
    • NavigationCancel事件:取消导航,如可用在路由守卫(Route Guards)中,拒绝用户访问此路由。
    • RoutesRecognized 事件:当URL与路由匹配时,触发此事件。
    • NavigationEnd 事件:在导航成功结束时触发。
  1. 使用命令ng g c first-cpnng g c second-cpn 分别创建组件 first-cpn.component.tssecond-cpn.component.ts ,如果是手动创建的这三个文件需要添加到 app.module.ts 文件中

  2. first-cpn.component.ts 文件内容如下

    import { Component, OnInit, OnDestroy } from '@angular/core';
    import { Router, RoutesRecognized } from '@angular/router';
    import { filter } from 'rxjs/operators';
    import { Subscription } from 'rxjs';
    
    @Component({
      selector: 'app-first-cpn',
      template: ` <p>first</p> `,
    })
    export class FirstCpnComponent implements OnInit, OnDestroy {
      private routeSubscription: Subscription;
    
      constructor(private router: Router) {}
    
      ngOnInit(): void {
        this.routeSubscription = this.router.events
          .pipe(filter((event) => event instanceof RoutesRecognized))
          .subscribe((event: RoutesRecognized) => {
            console.log('当前路由状态:', event.state);
          });
      }
    
      ngOnDestroy(): void {
        // 取消订阅
        this.routeSubscription.unsubscribe();
      }
    }
    
  3. second-cpn.component.ts 文件内容如下,注意:该组件有一个路由出口router-outlet 用于显示其子组件

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-second-cpn',
      template: `<p>second</p>`,
    })
    export class SecondCpnComponent {}
    
  4. 路由模块 src/app/app-routing.module.ts 文件如下

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    
    // 引入两个组件
    import { FirstCpnComponent } from './first-cpn/first-cpn.component';
    import { SecondCpnComponent } from './second-cpn/second-cpn.component';
    
    const routes: Routes = [
      {
        path: '',
        redirectTo: 'first-cpn',
        pathMatch: 'full',
      },
      {
        path: 'first-cpn',
        component: FirstCpnComponent,
      },
      {
        path: 'second-cpn',
        component: SecondCpnComponent,
      },
    ];
    
    @NgModule({
      imports: [
        RouterModule.forRoot(routes, {
          enableTracing: true, // 控制台输出所有路由器出发的事件
        }),
      ],
      exports: [RouterModule],
    })
    export class AppRoutingModule {}
    
  5. src/app/app.component.html 文件如下

    <nav>
     <a routerLink="./first-cpn" routerLinkActive="active" >跳转到 firstCpn 组件</a> | 
     <a routerLink="./second-cpn" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">跳转到 secondCpn 组件</a> 
    </nav>
    
    <router-outlet></router-outlet>
    
  6. 点击两个链接,跳转到不同页面,当路由处于 RoutesRecognized 状态时,会触发订阅事件

五、在路由中传递参数

[1]. 传递配置参数

  • 在配置路由时,用户可以通过路由配置中的 Route 对象的 data 属性来存放与每个具体路由有关的数据。该数据可以被任何一个 ActivatedRoute 对象访问,一般用来保存如页面标题、导航以及其他静态只读数据。

    const routes: Routes = [
      { path: 'first', component: FirstComponent, data: { title: 'First Page' } },
      {
        path: 'second',
        component: SecondComponent,
        data: { title: 'Second Page' },
      },
      { path: 'third', component: Thirdcomponent, data: { title: 'hird page' } },
      { path: '**', redirectTo: 'first' },
    ];
    
  • data 属性接收一个键/值对([name: string]: any)类型的数据对象;有多个键/值对时,以逗号分隔。在代码中,参数可通过 ActivatedRoute 对象的 Snapshot 属性获取。

    constructor(private actRoute: ActivatedRoute) {
      // 通过ActivatedRoute对象的snapshot属性获取参数
      this.title = this.actRoute.snapshot.data['title'];
      // 通过订阅获取
      this.actRoute.data.subscribe((data) => {
        console.log(data.title);
      });
    }
    

[2]. 传递路径参数

我们可以将路径参数作为 URL路径的一部分传递给路由配置中的组件。路径参数分为必选参数和可选参数,这涉及路由的定义。

1). 传递必选参数
  • 必选参数在 URL中的格式如 localhost:4200/user/123,其中 123 就是传递的必选参数。必选参数在路由配置中是这么定义的。

    { path:"user/:id',component:UserComponent}
    
  • 上面的代码创建了一个包含必选参数 id 的路由,这个路由中的“:id”相当于在路径中创建了一个空位,这个空位不补全是无法导航的。

    this.router.navigate(['/user']);//跳转错误,无效路由
    this.router.navigate(['/user', 123]);//正确跳转,跳转URL为/user/123
    
  • 在模板视图中,含有必选参数的路由是这么定义的,代码如下。

    <a [routerLink]="['/user',userId]">链接到user组件</a>
    
  • 在代码中,通过路由对象的 navigate()方法可导航到含有必选参数的路由。

    import {Router, ActivatedRoute} from '@angular/router';
    
    constructor(private router: Router, private actRoute: ActivatedRoute){}
    
    gotoUser() {
    	this.router.navigate(['/user',user.id]);//导航到user/123的路由
    }
    
    ngOnInit(){
    	this.user_id=this.actRoute.snapshot.params.id;// 通过快照对象的方式获取值
    	// 通过订阅获取
    	this.actRoute.params.subscribe((data)=>{
           this.user_id = data.id; 
     	})
    }
    
  • 用例参考下边的传递可选参数

2). 传递可选参数
  • 可选参数在 URL 中的格式如localhost:4200/users;a=123;b=234,其中 a=123;b=234 就是传递的可选参数。可选参数是 Web 应用程序在导航期间传递任意信息的一种方式。

  • 和必选参数一样,路由器也支持通过可选参数导航。在实际应用中一般是先定义完必选参数之后,再通过一个独立的对象来定义可选参数。 可选参数不涉及模式匹配,并在表达上具有巨大的灵活性。通常,对强制性的值(如用于区分两个路由路径的值)使用必选参数;当这个值是可选的、复杂的或多值的时,使用可选参数。可选参数的导航方式在 Web 应用程序中是这样的。

    // 正确跳转。不涉及模式匹配,参数可传可不传
    this.router.navigate(['/user']);
    //正确跳转,跳转URL为 localhost:4200/users;a=123;b=234
    this.router.navigate(['/user',{a:123,b:234}]);
    
  • 在模板视图中,含有可选参数的路由是这么定义的,代码如下,

    <a [routerLink]="['/users',{a:123, b: 234}]">返回</a>
    
  • 在代码中,通过路由对象的 navigate()方法可导航到含有可选参数的路由。

    import { Router, ActivatedRoute} from '@angular/router';
    
    constructor(private router: Router, private actRoute: ActivatedRoute) {}
    //导航到localhost:4200/users;a=123;b=234的路由
    gotoUser() {
    	this.router.navigate(['/users', {a:123, b:234}]);
    }
    
     ngOnInit() {
        this.actRoute.paramMap
          .pipe(map((params) => params.get('a')))
          .subscribe((data) => {
            console.log('a', data);
          });
      }
    
  1. 使用命令ng g c first-cpnng g c second-cpn 分别创建组件 first-cpn.component.tssecond-cpn.component.ts ,如果是手动创建的这三个文件需要添加到 app.module.ts 文件中

  2. src/app/app.component.html 文件如下, 跳转到 secondCpn 组件携带必选参数,跳转到 first组件携带可选参数

    <nav>
      <a [routerLink]="['/second-cpn', 123]">必选参数到 secondCpn 组件</a> |
      <a [routerLink]="['/first-cpn', { a: 123, b: 234 }]">可选参数到 first 组件</a>
    </nav>
    
    <router-outlet></router-outlet>
    
  3. first-cpn.component.ts 文件内容如下

    import { Component, OnInit } from '@angular/core';
    import { Router, ActivatedRoute } from '@angular/router';
    import { of } from 'rxjs';
    import { map } from 'rxjs/operators';
    
    @Component({
      selector: 'app-first-cpn',
      template: `<button (click)="gotoFirstCpn()">
        携带参数跳转到secondCpn组件
      </button>`,
    })
    export class FirstCpnComponent implements OnInit {
      constructor(private router: Router, private actRoute: ActivatedRoute) {}
    
      ngOnInit() {
        // this.actRoute.paramMap
        //   .pipe(map((params) => params.get('a')))
        //   .subscribe((data) => {
        //     console.log('a', data);
        //   });
    
        this.actRoute.params.subscribe((data) => {
          // {a: '123', b: '234'}
          console.log(data);
        });
      }
    
      //导航到second-cpn/123的路由
      gotoFirstCpn() {
        this.router.navigate(['/second-cpn', 123]);
      }
    }
    
  4. second-cpn.component.ts 文件内容如下,注意:该组件有一个路由出口router-outlet 用于显示其子组件

    import { Component } from '@angular/core';
    import { Router, ActivatedRoute } from '@angular/router';
    
    @Component({
      selector: 'app-second-cpn',
      template: `<p>{{ user_id }}</p>
        <button (click)="gotoFirstCpn()">携带可选参数到firstCpn组件</button>`,
    })
    export class SecondCpnComponent {
      user_id: number | string;
    
      constructor(private router: Router, private actRoute: ActivatedRoute) {}
      ngOnInit() {
        this.user_id = this.actRoute.snapshot.params.id; // 通过快照对象的方式获取值
        console.log(this.user_id);
      }
    
      gotoFirstCpn() {
        this.router.navigate(['/first-cpn',{a:123,b:234}]);
      }
    }
    
  5. 路由模块 src/app/app-routing.module.ts 文件如下

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    
    // 引入两个组件
    import { FirstCpnComponent } from './first-cpn/first-cpn.component';
    import { SecondCpnComponent } from './second-cpn/second-cpn.component';
    
    const routes: Routes = [
      {
        path: '',
        redirectTo: 'first-cpn',
        pathMatch: 'full',
      },
      {
        path: 'first-cpn',
        component: FirstCpnComponent,
      },
      {
        path: 'second-cpn/:id',
        component: SecondCpnComponent,
      },
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule],
    })
    export class AppRoutingModule {}
    
  6. 点击页面的链接或按钮,跳转到不同页面,浏览器控制台会显示各自获取到的数据

3). 传递查询参数
  • 查询参数在URL中的格式如localhost:4200/user?id=123,其中id=123就是传递的查询参数从技术角度讲,查询参数类似可选参数,也不涉及模式匹配,并在表达上具有巨大的灵活性。

  • 查询参数的导航方式在 Web 应用程序中是这样的。

    // 正确跳转。不涉及模式匹配,参数可传可不传
    this.router.navigate(['/user']);
    // 正确跳转,跳转URL为10calhost:4200/use?id=123
    this.router.navigate(['/user'],{ queryparams:{id:123} });
    
  • Router 对象的 navigate()方法的定义如下。

    navigate(commands: any[], extras?: NavigationExtras): Promise<boolean>;
    
    • navigate()方法的第二个参数类型是 NaviqationExtras 接口,它的定义如下
    export declare interface NavigationExtras {
      relativeTo?: ActivatedRoute | null;
      queryParams?: Params | null;
      fragment?: string;
      preserveQueryParams?: boolean;
      queryParamsHandling?: QueryParamsHandling | null;
      preserveFragment?: boolean;
      skipLocationChange?: boolean;
      replaceUrl?: boolean;
      state?: {
        [k: string]: any;
      };
    }
    
  • 通过 fragment 关键字传递参数。将会生成链接 /user/bob#education

    this,router,navigate(['/user',user.name],{ fragment: 'education'});
    
  • queryParamsHandling 参数的含义:是否需要将当前 URL中的查询参数传递给下一个路由,有三个参数。

    • merge:这是默认选项,将新的查询参数与现有的查询参数合并。如果有相同的参数名,则新的值将覆盖旧的值。
    • preserve: 保留现有的查询参数,忽略新的查询参数。这意味着导航过程中不会更改现有的查询参数。
    • ’ ’ (空字符串): 不处理查询参数。导航过程中不会传递任何查询参数。。
    //如当前的URL是 user;a=123?code=bbb,跳转之后的链接为 /others/1?code=bbb,
    this.router.navigate(['/others',1], {queryParamsHandling:'preserve'});
    
  • 页面接收查询参数

    this.activatedRoute.queryParams.subscribe((data) => {
     	this.id= data.id;
    });
    

[3]. 用例

  1. 使用如下命令创建一个新的带路由的项目

    ng new route-demo2 --routing 
    
  2. 使用如下命令创建两个组件和一个接口

    # 创建组件1
    ng g c user-list
    # 创建组件2
    ng g c user-detail
    # 创建接口 ng generate interface user-face 的缩写
    ng g i user-face 
    
  3. 修改接口文件 src/app/user-face.ts 如下

    export interface UserFace {
      id: number;
      name: string;
      email: string;
    }
    
  4. 修改路由模块 src/app/app-routing.module.ts 如下:

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    
    // 引入自己创建的组件
    import { UserListComponent } from './user-list/user-list.component';
    import { UserDetailComponent } from './user-detail/user-detail.component';
    
    const routes: Routes = [
      // 默认路径导航到用户列表页面
      {
        path: '',
        redirectTo: 'users',
        pathMatch: 'full',
      },
      // 用户列表页
      {
        path: 'users',
        // 子路由,
        children: [
          {
            path: '',
            component: UserListComponent,
            data: { title: '用户列表页面' }, // 传递配置参数
          },
          {
            path: ':id',
            component: UserDetailComponent,
            data: { title: '用户详情页面' }, // 传递配置参数
          },
        ],
      },
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule],
    })
    export class AppRoutingModule {}
    
  5. 编辑用户详细组件 src/app/user-detail/user-detail.component.ts

    import { Component, OnInit } from '@angular/core';
    import { Router, ActivatedRoute } from '@angular/router';
    
    @Component({
      selector: 'app-user-detail',
      template: `
        <h4>标题:{{ title }}</h4>
        <div (click)="gotoUser()">
          {{ userId }} | {{ userName }} | {{ userEmail }}
        </div>
        <div>
          <a [routerLink]="['/users', { a: 123, b: 234 }]">演示可选参数</a><br />
          <a
            [routerLink]="['/users', { a: 123, b: 234 }]"
            [queryParams]="{ c: 345 }"
            >演示查询参数</a
          >
        </div>
      `,
    })
    export class UserDetailComponent implements OnInit {
      title = ''; // 从路由配置参数获取
      userId: string; // 路径参数:必选参数
      userName: string; // 路径参数:可选参数
      userEmail: string;
    
      // 注入 Router 和 ActivatedRoute 对象
      constructor(private router: Router, private actRoute: ActivatedRoute) {}
    
      ngOnInit(): void {
        // 方式一:通过快照对象的方式获取来自路由配置的参数
        // this.title = this.actRoute.snapshot.data.title;
        // 方式二:通过订阅的方式获取路由配置的参数
        this.actRoute.params.subscribe((data) => {
          console.log(data);
    
          this.title = data.title;
        });
    
        let params = this.actRoute.snapshot.params;
        const { id, name, email } = params;
        this.userId = id;
        this.userName = name;
        this.userEmail = email;
      }
    
      // 导航到含有可选参数的路由
      gotoUser() {
        this.router.navigate(['/users', { a: 123, b: 234 }]);
      }
    }
    
  6. 编辑用户列表组件 src/app/user-list/user-list.component.ts

    import { Component, OnInit } from '@angular/core';
    import { ActivatedRoute } from '@angular/router';
    import { of } from 'rxjs';
    import { switchMap } from 'rxjs/operators';
    
    import { UserFace } from '../user-face';
    
    @Component({
      selector: 'app-user-list',
      template: `
        <h4>{{ title }}</h4>
        <div *ngFor="let item of users; let i = index">
          <p>
            {{ i + 1 }}、<a [routerLink]="['/users/', item.id, {name: item.name, email: item.email}]">{{ item.name }}</a>
          </p>
        </div>
      `,
    })
    export class UserListComponent implements OnInit {
      title = '';
      users: UserFace[] = [
        { id: 1, name: 'user001', email: 'email@user1.com' },
        { id: 2, name: 'user002', email: 'email@user2.com' },
        { id: 3, name: 'user003', email: 'email@user3.com' },
        { id: 4, name: 'user004', email: 'email@user4.com' },
        { id: 5, name: 'user005', email: 'email@user5.com' },
      ];
    
      constructor(private actRoute: ActivatedRoute) {}
    
      ngOnInit(): void {
        // 通过快照对象的方式获取来自路由配置的参数
        this.title = this.actRoute.snapshot.data.title;
        // 通过快照对象的方式获取来自路由路径的可选参数
        let params = this.actRoute.snapshot.params;
    
        // 订阅来自路由路径的可选参数
        this.actRoute.paramMap
          .pipe(switchMap((params) => of(params.get('a'))))
          .subscribe((data) => {
            console.log('a', data);
          });
    
        // 方式一:订阅来自路由路径的查询参数
        this.actRoute.queryParamMap
          .pipe(switchMap((params) => of(params.get('c'))))
          .subscribe((data) => {
            console.log('c', data);
          });
    
        // // 方式二:订阅来自路由路径的查询参数
        // this.actRoute.queryParams.subscribe((data) => {
        //   console.log(data);
    
        //   console.log('c', data.c);
        // });
      }
    }
    
  7. 编辑跟组件src/app/app.component.ts

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      template: `<router-outlet></router-outlet>`,
    })
    export class AppComponent {}
    
  8. 启动项目

    ng serve --open
    

六、路由守卫

[1]. 概述

  • Angular 中一共提供了5种不同类型的路由守卫,每种路由守卫都按特定的顺序被调用。路由器会根据使用路由守卫的类型来调整路由的具体行为,这5种路由守卫如下。
    • CanActivate 守卫,用来处理导航到某路由的逻辑。
    • CanActivateChild 守卫,用来处理导航到某子路由的逻辑。
    • CanDeactivate 守卫,用来处理从当前路由离开的逻辑。
    • Resolve 守卫,用来在路由激活之前获取业务数据。
    • CanLoad 守卫,用来处理异步导航到某特性模块的逻辑。
  • 使用如下命令生成路由守卫:执行命令时,终端窗口将提示用户选择需要实现哪种类型的路由守卫;用户也可以在命令行中添加选项参数 --implements=CanActivate 来指定要实现的路由守卫类型。
    ng generate guard 文件名 [参数]
    
  • 生成的文件路径:src/app/文件名.guard.ts, 用例的文件名为my-can-activate
    • @Injectable()装饰器用来提供依赖注入服务,元数据 providedIn 属性的值为 root,表示注入的服务是全局的单例服务,即 MyCanActivateGuard 类的服务可以在根模块或者其他模块中调用。
    • 由于执行命令时,选择的是CanActivate守卫,因此MyCanActivateGuard类实现 Canctivate 守卫。CanActivate守卫中默认canActivate()方法注入了两个参数ActivatedRouteSnapshot 类型的 next 参数和 RouterStateSnapshot 类型的 state 参数。该方法的返回值是 boolean | UrTree 类型的3种形式之一(Observable、Promise 和基本类型 )。初始化代码中没有任何业务逻辑,默认返回 true。
    import { Injectable } from '@angular/core';
    import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
    import { Observable } from 'rxjs';
    
    @Injectable({
      providedIn: 'root'
    })
    export class MyCanActivateGuard implements CanActivate {
      canActivate(
        next: ActivatedRouteSnapshot,
        state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
        return true;
      }
    }
    

[2]. 配置路由守卫

  • 有了路由守卫之后,用户可以在路由配置中添加路由守卫来守卫路由。路由守卫返回一个值,以控制路由器的行为。

    • 如果它返回 true,导航过程会继续。
    • 如果它返回 false,导航过程就会终止,且用户留在原地。
    • 如果它返回 UrlTree(Angular 中提供的解析 URL 的对象),则取消当前的导航,并且导航到返回的这个 UrlTree。
  • 在路由模块中,Route 接口提供了属性供配置具体的路由守卫

    interface Route {
      path?: string;
      pathMatch?: string;
      matcher?: UrlMatcher;
      component?: Type<any>;
      redirectTo?: string;
      outlet?: string;
      canActivate?: any[]; // 配置 CanActivate 守卫
      canActivateChild?: any[]; // 配置 CanActivateChild 守卫
      canDeactivate?: any[]; // 配置 CanDeactivate 守卫
      canLoad?: any[]; // 配置 CanLoad 守卫
      data?: Data;
      resolve?: ResolveData; // 配置 Resolve 守卫
      children?: Routes;
      loadChildren?: LoadChildren;
      runGuardsAndResolvers?: RunGuardsAndResolvers;
    }
    
  • 以canActivate 属性为例,它接收的是一个数组对象,因此可以配置一个或者多个 CanActivate守卫。配置一个 CanActivate 守卫的代码如下。其他路由守卫的配置与 CanActivate 守卫的配置类似。

     {
        path: 'users',
        children: [
          {
            path: '',
            component: UserListComponent,
            data: { title: '用户列表页面' },
          },
          {
            path: ':id',
            component: UserDetailComponent,
            data: { title: '用户详细页面' },
            canActivate: [MyCanActivateGuard], // 配CanActivate守卫
          },
        ],
      },
    
  • 如果配置了多个路由守卫,那么这些路由守卫会按照配置的先后顺序执行。如果所有路由守卫都返回 true,就会继续导航。如果任何一个路由守卫返回了 false,就会取消导航。如果任何一个路由守卫返回了 UrlTree,就会取消当前导航,并导航到这个路由守卫所返回的 UrlTree。

[3]. CanActivate 守卫应用

  • CanActivate 是一个实现 CanActivate 接口的路由守卫,该路由守卫决定当前路由能否激活。如果 CanActivate 守卫返回 true,就会继续导航,如果返回了 false,就会取消导航。如果返回了UrITree,就会取消当前导航,并转向导航到返回的 UrlTree。
  • CanActivate 接口中有个canActivate() 方法,该接口的定义如下。
    interface CanActivate {
      canActivate(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
      ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
    }
    
  • canActivate()方法中注入了两个类型的参数,可以直接在方法中调用这些参数的属性或方法完成具体的业务逻辑。这两个参数的类型分别是 RouterStateSnapshotActivatedRouteSnapshot,我们已经知道 RouterState 对象维护的是一个全局路由器状态树,ActivatedRoute 对象维护的是激活路由状态树。那么,RouterStateSnapshotActivatedRouteSnapshot 代表的是这两个状态树的瞬时状态。
  • canActivate() 方法的返回值是 boolean | UrTree 类型的3 种形式之一。CanActivate 守卫一般用来对用户进行权限验证,如判断是否是登录用户、判断凭证是否有效等。

[4]. CanActivateChild 守卫应用

  • CanActivateChild 守卫实现 CanActivateChild 接口,该路由守卫决定当前路由的子路由能否被激活。CanActivateChild 守卫的应用场景与 CanActivate 守卫类似,不同之处在于CanActivate 守卫保护的是当前路由,而 CanActivateChild 守卫配置在父路由上,对它的子路由进行保护。

    {
    	path: 'users',
    	canActivate: [MyCanActivateChildGuard], // 配CanActivateChild守卫
    	children: [
    		{
    			path: '',
    			component: UserListComponent,
    			data: { title: '用户列表页面' },
    		},
    		{
    			path: ':id',
    			component: UserDetailComponent,
    			data: { title: 用户详细页面 },
    			canActivate: [MyCanActivateGuard], // 配CanActivate守卫
    		},
    	],
    },
    

[5]. CanDeactivate 守卫应用

  • CanDeactivate 守卫实现 CanDeactivate 接口,该路由守卫用来处理从当前路由离开的逻辑,应用的场景一般是提醒用户执行保存操作后才能离开当前页面。CanDeactivate 接口中有canDeactivat() 方法,该接口的定义如下。

    interface CanDeactivate<T> {
      canDeactivate(
        component: T,
        currentRoute: ActivatedRouteSnapshot,
        currentState: RouterStateSnapshot,
        nextState?: RouterStateSnapshot
      ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
    }
    
  • canDeactivate()方法的第一个参数就是 CanDeactivate接口指定的泛型类型的组件,可以直接调用该组件的属性或方法,如根据要保护的组件的状态,或者调用方法来决定用户是否能够离开。

[6]. Resolve 守卫应用

  • Resolve 守卫实现 Resolve 接口,该路由守卫用来在路由激活之前获取业务数据。Resolve接口中有 resolve()方法,该接口的定义如下。

    interface Resolve<T> {
      resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T;
    }
    
  • 该路由守卫一般应用在 HTTP 请求数据返回有延迟,导致模板视图无法立刻显示的场景中。如HTTP 请求数据返回之前模板上所有需要用插值表达式显示值的地方都是空的,这会造成用户体验不佳。Resolve 守卫的解决办法是,在进入路由之前 Resolve 守卫先去服务器读数据,把需要的数据都读好以后,带着这些数据再进入路由,立刻把数据显示出来。

  • resolve()方法返回的值是泛型类型,它一般对应着组件视图中的数据对象。该数据对象存储在路由器状态中,在组件类中可以通过下面的方式获取。

    constructor(private route: ActivatedRoute) {}
    ngOnInit() {
    	// 通过订阅的方式获取resolve() 方法返回的值
    	this.route.data.subscribe(data => this.users = data.users); 
    }
    

[7]. CanLoad 守卫应用

  • CanLoad 守卫实现 CanLoad 接口,该路由守卫用来处理异步导航到某特性模块的逻辑。CanLoad 接口中有 canLoad() 方法,该接口的定义如下。

    interface CanLoad {
        canLoad(route: Route, segments: UrlSegment[]): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
    }
    
  • 在业务场景中,CanLoad 守卫用来保护对特性模块的未授权加载,如在路由配置中,配置CanLoad 守卫来保护是否加载路由。

    {
    	path: 'admin',
    	loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
    	canLoad: [AuthGuard]
    }
    
  • 上述配置中,loadChildren 属性中的语法是异步延迟加载模块,在 CanLoad 守卫中代码如下

    canload(route: Route): boolean{
    	// route为准备访问的目的地址
    	let url =`${route.path}`;  
    	 // 判断是否继续加载,返回boolean
    	 return this.checkLogin(url); 
    }
    

六、路由器的延迟加载(异步加载)

[1]. 概述

  • Angular 是通过模块来处理延迟加载的。每个 Web 应用程序都有一个名为 NgModule 类的根模块,根模块位于 Web 应用程序的 app.module.ts 文件中,并包含所有导入模块和组件声明。根模块中导入的所有模块是在编译时捆绑在一起并推送到浏览器的。默认情况下,模块的 NgModule类都是急性加载的,也就是说所有模块会在 Web 应用程序加载时一起加载,无论是否立即使用它们。因此,当 Web 应用程序想要促进延迟加载时,需要将根模块分成若干个较小的特性模块,然后仅将最重要的特性模块首先加载到根模块中。
  • Angular 的路由器提供了延迟加载功能:一种按需加载根模块的模式。延迟加载本质上可以缩小初始加载包的尺寸,从而减少 Web 应用程序的初始加载时间。对配置有很多路由的大型 Web 应用程序,推荐使用延迟加载功能。

[2]. 实施延迟加载

  • 所谓延迟加载是指延迟加载特性模块,因此在 Web 应用程序中除了根模块外,至少需要一个额外的特性模块。实施延迟加载特性模块有3 个主要步骤。
    • (1)创建一个带路由的特性模块
    • (2)删除默认的急性加载。
    • (3)配置延迟加载的路由。
  • 实际上,以上3 个步骤可以通过一条 Angular CLI 命令完成
    • 注意区分 ng generate module 命今中的选项 --route 与 选项 --routing,前者是创建延迟加载路由,后者是创建普通的路由。如果两者同时使用,–route 选项将覆盖 --routing 选项
    ng generate module 模块名 --route 延迟加载特性模块的路径 --module app.module
    
  1. 使用ng generate module 命令附带 --route 选项时,该命令将告诉 Angular CLl命令,新建一个延迟加载的特性模块,并且不需要在根模块中对其引用。 命令中的 --route 选项参数 featurepath 表示将生成一个路径为 featurepath 的延迟加载路由,并且将其添加到由–module 选项指定的模块声明的 routes 数组中。命令中的 --module 选项参数 app 表示在指定的模块文件(省略了扩展名,指的是 app.module.ts 模块文件)中添加延迟加载路由的配置。

    ng generate module my-feature --route featurepath --module app
    
  2. 打开 src/app/app-routing.module.ts 文件可以看到新创建的特性模块的路由已经添加进 routes 数组。延迟加载使用 Routed 对象的 loadChildren 属性,其后是一个使用浏览器内置的 import(…) 语去进行动态导入的函数。其导入路径是到当前模块的相对路径。

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    
    const routes: Routes = [
      { path: 'featurepath', loadChildren: () => import('./my-feature/my-feature.module').then(m => m.MyFeatureModule) },
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule],
    })
    export class AppRoutingModule {}
    
  3. 打开 src/app/my-feature/my-feature-routing.module.ts可以看到如下内容,Angular CLI 命令把 RouterModule.forRoot(routes)方法添加到根模块路由中,而把 RouterModule.forChild(routes)方法添加到各个特性模块中。这是因为forRoot(routes)方法将会注册并返回一个全局的单例 RouterModule 对象,所以在根模块中必须只使用一次 forRoot()方法,各个特性模块中应当使用 forChild()方法

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    
    import { MyFeatureComponent } from './my-feature.component';
    
    const routes: Routes = [{ path: '', component: MyFeatureComponent }];
    
    @NgModule({
      imports: [RouterModule.forChild(routes)],
      exports: [RouterModule]
    })
    export class MyFeatureRoutingModule { }
    

[3]. 用例

  1. 新建一个项目
    参数参考:https://v10.angular.cn/cli/new
    - -minimal: 不需要测试框架
    -s:- -inlineStyle 别名,内联样式
    -t:- -inlineTemplate 别名,内联模板

    ng new demo-load-route --routing --minimal --style=css -s -t
    
  2. 常见两个特性模块:将在 src/app 目录下生成 features 文件夹并再文件夹下生成对应的目录

    #创建带路由的users模块,并且配置为延迟加载模块
    ng g m features/users --route users --module app.module  
    #创建带路由的posts模块,并目配置为延迟加载模块
    ng g m features/posts --route posts --module app.module 
    

    每个命令会创建三个文件(如果新建项目时 没有带 --minimal 参数,会多生成一个 .spec.ts 结尾的测试文件)
    在这里插入图片描述

  3. 此时src/app/app-routing.module.ts 文件内容如下:

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    
    const routes: Routes = [
      { path: 'users', loadChildren: () => import('./features/users/users.module').then((m) => m.UsersModule) },
      { path: 'posts', loadChildren: () => import('./features/posts/posts.module').then((m) => m.PostsModule) }
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
    })
    export class AppRoutingModule {}
    
  4. 修改 src/app/app.component.ts 内容如下

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      template: `
        <h1>{{title}}</h1>
        <button routerLink="/users">用户信息</button>
        <button routerLink="/posts">留言信息</button>
        <button routerLink="/">主页</button>
        <router-outlet></router-outlet>
      `,
      styles: []
    })
    export class AppComponent {
      title = 'demo-load-route';
    }
    
  5. 在点击按钮切换不同的模块时,可以在浏览器控制台的网络中看到第一次点击后才加载对应的路由(多次点击只在第一次加载)
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2274346.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MATLAB】绘制投资组合的有效前沿

文章目录 一、数据准备二、有效前沿三、代码3.1 数据批量读取、预处理3.2 绘制可行集3.3 绘制有效前沿3.4 其它-最大夏普率 一、数据准备 准备多个股票的的历史数据&#xff0c;目的就是找到最优的投资组合。 下载几个标普500里面的公式的股票数据吧&#xff0c;下载方法也可…

JuiceFS 2024:开源与商业并进,迈向 AI 原生时代

即将过去的 2024 年&#xff0c;是 JuiceFS 开源版本推出的第 4 年&#xff0c;企业版的第 8 个年头。回顾过去这一年&#xff0c;JuiceFS 社区版依旧保持着快速成长的势头&#xff0c;GitHub 星标突破 11.1K&#xff0c;各项使用指标增长均超过 100%&#xff0c;其中文件系统总…

重生之我在异世界学编程之C语言:枚举联合篇

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文枚举&#xff08;Enum&#xff0…

6 分布式限流框架

限流的作用 在API对外互联网开放的情况下&#xff0c;是无法控制调用方的行为的。当遇到请求激增或者黑客攻击的情况下&#xff0c;会导致接口占用大量的服务器资源&#xff0c;使得接口响应效率的降低或者超时&#xff0c;更或者导致服务器宕机。 限流是指对应用服务进行限制…

【Linux系列】如何使用 nohup 命令在后台运行脚本

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

门禁系统与消防报警的几种联动方式

1、规范中要求的出入口系统与消防联动 1.1《建筑设计防火规范》GB 50016-2018 1.2《民用建筑电气设计规范》JGJ 16-2008  14.4出入口控制系统 3 设置在平安疏散口的出入口限制装置&#xff0c;应与火灾自动报警系统联动;在紧急状况下应自动释放出入口限制系统&…

Express 加 sqlite3 写一个简单博客

例图&#xff1a; 搭建 命令&#xff1a; 前提已装好node.js 开始创建项目结构 npm init -y package.json:{"name": "ex01","version": "1.0.0","main": "index.js","scripts": {"test": &q…

GetMaterialApp组件的功能与用法

文章目录 1. 知识回顾2. 使用方法2.1 源码分析2.2 常用属性3. 示例代码4. 内容总结我们在上一章回中介绍了"Get包简介"相关的内容,本章回中将介绍GetMaterialApp组件.闲话休提,让我们一起Talk Flutter吧。 1. 知识回顾 我们在上一章回中已经介绍过GetMaterialApp组…

LabVIEW之树形控件

一、树形控件基本构成 树形控件这个名称非常形象&#xff0c;其如同树一样&#xff0c;是典型的分层结构。树形控件的属性和方法使用非常灵活&#xff0c;树形控件的内容既可以静态编辑&#xff0c;也可以通过编程来动态填充。静态编辑树形控件适用于内容不变的应用场景&#…

Inno Setup制作安装包,安装给win加环境变量

加 ; 加环境变量&#xff0c;开启&#xff0c;下面一行 ChangesEnvironmentyes 和 ; 加环境变量wbrj变量名&#xff0c;{app}\project\bin变量值&#xff0c;{app}\后接文件名&#xff0c;{app}表示安装路径。下面一行,{olddata};原来的值上拼接 Root: HKLM; Subkey: “SYSTEM\…

张朝阳惊现CES展,为中国品牌 “代言”的同时,或将布局搜狐新战略!

每年年初&#xff0c;科技圈的目光都会聚焦在美国拉斯维加斯&#xff0c;因为这里将上演一场被誉为 “科技春晚” 的年度大戏 ——CES 国际消费电子展。作为全球规模最大、最具影响力的科技展会之一&#xff0c;CES 吸引了来自 160 多个国家的创新者和行业领导者&#xff0c;是…

UDS诊断之0x27服务—结合实例讲解

前言&#xff1a; 本文讲解的是比较深入一点知识&#xff0c;对于一些刚入门的同学&#xff0c;建议直接先看一遍14229规范&#xff0c;然后找一个实际项目练练手&#xff01;然后再来看本文&#xff0c;相信你会对0x27服务有更深的认知&#xff01;&#xff01;&#xff01; …

React Router 向路由组件传state参数浏览器回退历史页面显示效果问题

昨天在看尚硅谷张天禹老师讲的 React教程p90&#xff0c;老师讲到 React路由的 replace模式和push模式&#xff0c;老师的演示效果与自己本地操作不太一样。 老师的效果&#xff1a;点击查看消息1&#xff0c;消息2&#xff0c;消息3 再点回退&#xff0c;可以依次查看到 消息…

静态路由配置与调试——计算机网络实训day1

文章目录 操作前准备一、实验目的二、实验要求三、实验过程1、在R1和R2上配置设备名称。基本配置设备命名 2、在R1和R2上配置接口IP地址&#xff0c;并查看IP地址的配置情况。3、在R1和R2上配置静态路由&#xff0c;并查看路由表。静态路由缺省路由&#xff08;默认路由&#x…

【HeadFirst系列之HeadFirst设计模式】第1天之HeadFirst设计模式开胃菜

HeadFirst设计模式开胃菜 前言 从今日起&#xff0c;陆续分享《HeadFirst设计模式》的读书笔记&#xff0c;希望能够帮助大家更好的理解设计模式&#xff0c;提高自己的编程能力。 今天要分享的是【HeadFirst设计模式开胃菜】&#xff0c;主要介绍了设计模式的基本概念、设计模…

UOS系统和windows系统wps文档显示差异问题解决

最近在使用UOS系统的过程中&#xff0c;发现了一个很有意思的现象。就是在UOS系统上编辑的文档&#xff0c;发到windows系统上&#xff0c;会出现两个文档显示差异很大的情况&#xff0c;文档都是使用一样的wps软件打开的。到底是什么原因导致这种现象的呢&#xff1f;该如何解…

网络应用层HTTP协议

网络应用层HTTP协议 1. HTTP协议介绍 在互联网世界中&#xff0c;HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信&#xff0c;以交换或传输超文本(如 HTML 文档)。…

B+树的原理及实现

文章目录 B树的原理及实现一、引言二、B树的特性1、结构特点2、节点类型3、阶数 三、B树的Java实现1、节点实现2、B树操作2.1、搜索2.2、插入2.3、删除2.4、遍历 3、B树的Java实现示例 四、总结 B树的原理及实现 一、引言 B树是一种基于B树的树形数据结构&#xff0c;它在数据…

在 macOS 中,设置自动将文件夹排在最前

文章目录 1、第一步访达设置2、第二步排序方式 需要两步设置 1、第一步访达设置 按名称排序的窗口中 2、第二步排序方式 选择名称

【数据库】Unity 使用 Sqlite 数据库

1.找到需要三个 DLL Mono.Data.Sqlite.dllSystem.Data.dllsqlite3.dll 上面两个dll可在本地unity安装目录找到&#xff1a; C:\Program Files\Unity\Hub\Editor\2022.3.xxf1c1\Editor\Data\MonoBleedingEdge\lib\mono\unityjit-win32 下面dll可在sqlite官网下载到&#xff…