[Angular] Day32. Lazy-loading feature modules

在默认情况下 NgModules 都是急切加载的,这意味着一但应用程序加载,所有的 NgModules 无论是否需要都会被载入,但对於许多大型的应用程序而言可以考虑使用延迟加载功能,他是一种根据需要才加载 NgModules 的设计模式,延迟加载有助於让初始化的档案保持为较小的大小,从而减少加载的时间,下面就来介绍该如何使用延迟加载模式吧。

https://ithelp.ithome.com.tw/upload/images/20210905/201247675kZ9WQlQIh.jpg


Step-by-step setup

要设置延迟加载的 feature module 主要有两个步骤:

  1. 使用 --route 通过 Angular CLI 创建 feature module
  2. 配置 routes

接着来看看该怎麽创建一个新的延迟载入的 feature module 吧

Create a feature module with routing

首先需要创建一个带有 route 功能的 feature module,并这个 route 的路径要指定到 feature module 宣告的某一个 component,可以使用 Angular CLI 一次就完成所有动作,所以是不是很需要 Angular CLI 呢( gif

ng generate module customers --route customers --module app.module

上面的 CLI Command 会创建一个名为 customers 的文件夹,其中的 customers.module.ts 中会定义一个延迟载入的 feature module 名为 CustomersModule,还会创建一个用於定义 route 的 route module 名为 CustomersRoutingModule,除此之外还会建立一个 CustomerComponent 并自动导入到 CustomersRoutingModule 的 declarations 中。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { CustomersRoutingModule } from './customers-routing.module';
import { CustomersComponent } from './customers.component';

@NgModule({
  declarations: [
    CustomersComponent
  ],
  imports: [
    CommonModule,
    CustomersRoutingModule
  ]
})
export class CustomersModule { }
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CustomersComponent } from './customers.component';

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

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

由於这个新的 feature module 是延迟加载的所以这个 Command 不会在 root module 中添加对这个 fearute module 的引用,因为如果将它加入到 root module 的 imports 的话就会变成立即加载,相反的他会将这个 feature module 添加到 route 中

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  {
    path: 'customers',
    loadChildren: () =>
      import('./customers/customers.module').then((m) => m.CustomersModule),
  },
];

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

注意 loadChildren 的用法,他後面需要加一个函数,这个函数会使用浏览器内置的 import() 语法进行动态导入,导入 module 的路径是相对路径

Set up the UI

设定完延迟载入的 module 後,接着来设定一下 UI 画面让我们等等可以测试这个延迟载入是否成功,在 app.component.html 中设定按钮并加上 <router-outlet> 用於显示 route 的 component

<h1>{{title}}</h1>

<button type="button" class="btn btn-primary" routerLink="/customers">Customers</button>
<button type="button" class="btn btn-success" routerLink="">Home</button>

<router-outlet></router-outlet>

https://ithelp.ithome.com.tw/upload/images/20210905/20124767Ws1vG3NoVD.png

接着来测试一下会不会当点击 Customers 按钮後才会加载 feature module 吧,首先先先打开浏览器的开发者页面并选到 Network 页面,可以将前面载入的资讯先去除以便比较清楚的看到加载过程

img

可以看到当我们点击了 Customers 按钮後才会载入延迟载入的 feature module。


forRoot() and forChild()

如果使用 Angular CLI 创建一个 root module 的话,你会看到他将 RouterModule.forRoot(route) 添加到 AppRoutingModule 的 imports 中,这会让 Angular 知道 AppRoutingModule 是一个 route module,而 forRoot() 指定这是 root route module,他会配置传递给他的所有 route 让你可以访问 route directive 并注册 route service,在整个应用程序中 AppRoutingModule 只会使用 forRoot() 一次。

Angular CLI 还会将 RouterModule.forChild(routes) 添加到 feature module 的 imports 中,这样 Angular 就会知道这个只是负责提供额外的 route,并是针对 feature module 的,所以你可以在多个 route modules 中使用 forChild()。


Provider service in lazy-loading modules

在前几篇中提到了如果在 root module 中提供 service 时 Angular 会将它建立成一个单一的共享的 service 实例,所以将 service provider 添加到 root module 後,代表所有地方都可以将它注入并且使用。

但是如果是将 service provider 到 lazy-loading 的 module 时,由於 lazy-loading 的特性,因为这个 module 还没被载入所以这个 lazy-loading 中添加的 service 就无法在还没被载入的时候被其他地方的 module 使用,举个例子

  1. 先新增一个 service 并添加一个简单的 method

    import { Injectable } from '@angular/core';
    import { CustomersModule } from './customers.module';
    
    @Injectable()
    export class CustomersService {
    
      constructor() { }
    
      getName() {
        return 'Fandix Huang';
      }
    }
    
  2. 接着在 CustomersModule 的 providers 添加 CustomersService

    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    
    import { CustomersRoutingModule } from './customers-routing.module';
    import { CustomersComponent } from './customers.component';
    import { CustomersService } from './customers.service';
    
    @NgModule({
      declarations: [
        CustomersComponent
      ],
      imports: [
        CommonModule,
        CustomersRoutingModule
      ],
      providers: [CustomersService]
    })
    export class CustomersModule { }
    

这时候如果在 app.component.ts 中注入这个 service 的话就会出错,因为他被 provide 在 lazy-loading 的 module 中,所以还没被载入的话大家都不知道这个 service 是什麽

import { Component, OnInit } from '@angular/core';
import { CustomersService } from './customers/customers.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'Angular blog';
  constructor(private customersService: CustomersService) {}

  ngOnInit() {
    console.log(this.customersService.getName())
  }
}

https://ithelp.ithome.com.tw/upload/images/20210905/20124767YrfNPAxGVj.png

但是如果是注入在 CustomersModule 中 declarations 的 component 中的话就可以正常使用

import { Component, OnInit } from '@angular/core';
import { CustomersService } from './customers.service';

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

  constructor(private customerService: CustomersService) { }

  ngOnInit(): void {
    console.log(this.customerService.getName())
  }
}

https://ithelp.ithome.com.tw/upload/images/20210905/20124767GKOxZkfphj.png


结论

本章中介绍了如何建立与使用延迟载入的 module,虽然在中小专案中可以让所有 module 都是立即载入的,但随着专案变大这样所有 module 都是立即载入的话就会让初始档案变得非常大,因为应用程序一开始就要载入全部的 module,会让整个应用程序的载入变得非常慢,这时候就需要使用延迟载入的功能,当有需要才载入对应的 module 这样可以让一开始的专案维持在比较小的大小,从而加快应用程序的载入速度。

本章是介绍 module 的最後一张,下一章将会介绍也是常重要的一个部分,在现代的网页中都会需要串接後端的 API 以获得数据或是储存数据到 database,这些操作都需要与後端沟通,Angular 提供了一个方式让我们可以与後端进行沟通,那就是 HttpClient 详细的内容就留到明天讲解吧,那就明天见


Reference


<<:  Progressive Web App 定期背景同步 (19)

>>:  【从零开始的Swift开发心路历程-Day20】简易订单系统Part4(完)

Android 逆向工程 - 不确定 App 是否有混淆,所以反组译看看 ( 4步骤 )

免责声明 我花了大量的私人时间替专案研究 App 是否混淆成功,混淆结果是否达到需求。虽然我只会做简...

Day 15 - 用 useReducer 取代 useState !?

如果有错误,欢迎留言指教~ Q_Q 上篇提到,如何取得更新後的 state useReducer ...

Day29 X 面对高流量,前端可以做些什麽?

在现今的 Web 应用中,要建构一个稳定的大型系统,能够处理 High Concurrency 的...

【Day16-搜寻】茫茫文海当中找到那个对的词——文字处理利器之正规表达式在python的应用

前一天我们就如何让程序可以认得不同的单字稍微讨论了一下一些基本的处理,那今天我们就继续文字的主题来介...

铁人赛 Day1 -- HTML基本架构

哎呀,自学了两个月後刚好碰到2021的铁人赛开打,顺便来分享一下我的学习过程好了,有错的在劳烦各位大...