惰性加载的特性模块

Lazy Loading Feature Modules

前提条件

Prerequisites

对下列知识有基本的了解:

A basic understanding of the following:

如果需要本页描述的具有两个惰性加载模块的范例应用,参见在线例子 / 下载范例

For the final sample app with two lazy loaded modules that this page describes, see the在线例子 / 下载范例.


高层视角

High level view

要想建立一个惰性加载的特性模块,有三个主要步骤:

There are three main steps to setting up a lazy loaded feature module:

  1. 创建该特性模块。

    Create the feature module.

  2. 创建该特性模块的路由模块。

    Create the feature module’s routing module.

  3. 配置相关路由。

    Configure the routes.

建立应用

Set up an app

如果你还没有应用,可以遵循下面的步骤使用 CLI 创建一个。如果已经有了,可以直接跳到 配置路由部分。 输入下列命令,其中的 customer-app 表示你的应用名称:

If you don’t already have an app, you can follow the steps below to create one with the CLI. If you do already have an app, skip to Configure the routes. Enter the following command where customer-app is the name of your app:

ng new customer-app --routing
      
      ng new customer-app --routing
    

这会创建一个名叫 customer-app 的应用,而 --routing 标识生成了一个名叫 app-routing.module.ts 的文件,它是你建立惰性加载的特性模块时所必须的。 输入命令 cd customer-app 进入该项目。

This creates an app called customer-app and the --routing flag generates a file called app-routing.module.ts, which is one of the files you need for setting up lazy loading for your feature module. Navigate into the project by issuing the command cd customer-app.

创建一个带路由的特性模块

Create a feature module with routing

接下来,你需要一个要路由到的特性模块。要生成一个,请输入下列命令,其中的 customers 是该模块的名字:

Next, you’ll need a feature module to route to. To make one, enter the following command at the terminal window prompt where customers is the name of the module:

ng generate module customers --routing
      
      ng generate module customers --routing
    

这会创建一个 customers 目录,其中有两个文件:CustomersModuleCustomersRoutingModuleCustomersModule 扮演的是与客户紧密相关的所有事物的管理员。CustomersRoutingModule 则会处理任何与客户有关的路由。 这样就可以在应用不断成长时保持应用的良好结构,并且当复用本模块时,你可以轻松的让其路由保持完好。

This creates a customers folder with two files inside; CustomersModule and CustomersRoutingModule. CustomersModule will act as the gatekeeper for anything that concerns customers. CustomersRoutingModule will handle any customer-related routing. This keeps the app’s structure organized as the app grows and allows you to reuse this module while easily keeping its routing intact.

CLI 会把 CustomersRoutingModule 自动导入到 CustomersModule。它会在文件的顶部添加一条 JavaScript 的 import 语句,并把 CustomersRoutingModule 添加到 @NgModuleimports 数组中。

The CLI imports the CustomersRoutingModule into the CustomersModule by adding a JavaScript import statement at the top of the file and adding CustomersRoutingModule to the @NgModule imports array.

向特性模块中添加组件

Add a component to the feature module

要想在浏览器中看出该模块惰性加载成功了,就创建一个组件用来在应用加载 CustomersModule 之后渲染出一些 HTML。在命令行中输入如下命令:

In order to see the module being lazy loaded in the browser, create a component to render some HTML when the app loads CustomersModule. At the command line, enter the following:

ng generate component customers/customer-list
      
      ng generate component customers/customer-list
    

这会在 customers 目录中创建一个名叫 customer-list 的文件夹,其中包含该组件的四个文件。

This creates a folder inside of customers called customer-list with the four files that make up the component.

就像路由模块一样,CLI 也自动把 CustomerListComponent 导入了 CustomersModule

Just like with the routing module, the CLI imports the CustomerListComponent into the CustomersModule.

再添加一个特性模块

Add another feature module

为了提供另一个可路由到的地点,再创建第二个带路由的特性模块:

For another place to route to, create a second feature module with routing:

ng generate module orders --routing
      
      ng generate module orders --routing
    

这会创建一个名叫 orders 的新文件夹,其中包含 OrdersModuleOrdersRoutingModule

This makes a new folder called orders containing an OrdersModule and an OrdersRoutingModule.

现在,像 CustomersModule 一样,给它添加一些内容:

Now, just like with the CustomersModule, give it some content:

ng generate component orders/order-list
      
      ng generate component orders/order-list
    

建立 UI

Set up the UI

虽然你也可以在地址栏中输入 URL,不过导航菜单会更好用,而且更常见。 把 app.component.html 中的占位脚本替换成一个自定义的导航,以便你在浏览器中能轻松地在模块之间导航。

Though you can type the URL into the address bar, a nav is easier for the user and more common. Replace the default placeholder markup in app.component.html with a custom nav so you can easily navigate to your modules in the browser:

<h1> {{title}} </h1> <button routerLink="/customers">Customers</button> <button routerLink="/orders">Orders</button> <button routerLink="">Home</button> <router-outlet></router-outlet>
src/app/app.component.html
      
      <h1>
  {{title}}
</h1>

<button routerLink="/customers">Customers</button>
<button routerLink="/orders">Orders</button>
<button routerLink="">Home</button>

<router-outlet></router-outlet>
    

要想在浏览器中看到你的应用,就在终端窗口中输入下列命令:

To see your app in the browser so far, enter the following command in the terminal window:

ng serve
      
      ng serve
    

然后,跳转到 localhost:4200,这时你应该看到 “app works!” 和三个按钮。

Then go to localhost:4200 where you should see “app works!” and three buttons.

three buttons in the browser

要想让这些按钮生效,你需要配置一下这些路由模块。

To make the buttons work, you need to configure the routing modules.

配置路由

Configure the routes

这两个特性模块(OrdersModuleCustomersModule)应该挂接到 AppRoutingModule 中,来让路由器知道它们。其结构如下:

The two feature modules, OrdersModule and CustomersModule, have to be wired up to the AppRoutingModule so the router knows about them. The structure is as follows:

lazy loaded modules diagram

每个特性模块都是路由器中的一个“门口”。在 AppRoutingModule 中,你配置了一些路由指向这些特性模块(即 OrderModuleCustomersModule)。 通过这种方式,路由器就知道了如何跳转到特性模块。然后,特性模块就把 AppRoutingModuleCustomersRoutingModuleOrdersRoutingModule 连接到一起。这些路由模块会告诉路由器该到哪里去加载相应的组件。

Each feature module acts as a doorway via the router. In the AppRoutingModule, you configure the routes to the feature modules, in this case OrdersModule and CustomersModule. This way, the router knows to go to the feature module. The feature module then connects the AppRoutingModule to the CustomersRoutingModule or the OrdersRoutingModule. Those routing modules tell the router where to go to load relevant components.

顶层的路由

Routes at the app level

AppRoutingModule 中,把 routes 数组修改成这样:

In AppRoutingModule, update the routes array with the following:

const routes: Routes = [ { path: 'customers', loadChildren: './customers/customers.module#CustomersModule' }, { path: 'orders', loadChildren: './orders/orders.module#OrdersModule' }, { path: '', redirectTo: '', pathMatch: 'full' } ];
src/app/app-routing.module.ts
      
      const routes: Routes = [
  {
    path: 'customers',
    loadChildren: './customers/customers.module#CustomersModule'
  },
  {
    path: 'orders',
    loadChildren: './orders/orders.module#OrdersModule'
  },
  {
    path: '',
    redirectTo: '',
    pathMatch: 'full'
  }
];
    

这些 import 语句没有变化。前两个路径分别路由到了 CustomersModuleOrdersModule。注意看惰性加载的语法:loadChildren 后面紧跟着一个字符串,它指向模块的相对路径,然后是一个 #,然后是该模块的类名。

The import statements stay the same. The first two paths are the routes to the CustomersModule and the OrdersModule respectively. Notice that the lazy loading syntax uses loadChildren followed by a string that is the relative path to the module, a hash mark or #, and the module’s class name.

特性模块内部

Inside the feature module

接下来看看 customers.module.ts。如果你使用的是 CLI,并遵循本页面中给出的步骤,那么在这里你不必做任何事。 特性模块就像是 AppRoutingModule 和该特性自己的路由模块之间的连接器。 AppRoutingModule 导入了特性模块 CustomersModule,而 CustomersModule 又导入了 CustomersRoutingModule

Next, take a look at customers.module.ts. If you’re using the CLI and following the steps outlined in this page, you don’t have to do anything here. The feature module is like a connector between the AppRoutingModule and the feature routing module. The AppRoutingModule imports the feature module, CustomersModule, and CustomersModule in turn imports the CustomersRoutingModule.

import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { CustomersRoutingModule } from './customers-routing.module'; import { CustomerListComponent } from './customer-list/customer-list.component'; @NgModule({ imports: [ CommonModule, CustomersRoutingModule ], declarations: [CustomerListComponent] }) export class CustomersModule { }
src/app/customers/customers.module.ts
      
      import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CustomersRoutingModule } from './customers-routing.module';
import { CustomerListComponent } from './customer-list/customer-list.component';

@NgModule({
  imports: [
    CommonModule,
    CustomersRoutingModule
  ],
  declarations: [CustomerListComponent]
})
export class CustomersModule { }
    

customers.module.ts 文件导入了 CustomersRoutingModuleCustomerListComponent,所以 CustomersModule 类可以访问它们。 接着 CustomersRoutingModule 出现在了 @NgModuleimports 数组中,这让 CustomersModule 可以访问它的路由模块。而 CustomerListComponent 出现在了 declarations 数组中,这表示 CustomerListComponent 属于 CustomersModule

The customers.module.ts file imports the CustomersRoutingModule and CustomerListComponent so the CustomersModule class can have access to them. CustomersRoutingModule is then listed in the @NgModule imports array giving CustomersModule access to its own routing module, and CustomerListComponent is in the declarations array, which means CustomerListComponent belongs to the CustomersModule.

配置该特性模块的路由

Configure the feature module’s routes

接下来的步骤位于 customers-routing.module.ts 中。首先,在文件的顶部使用 JS 的 import 语句导入该组件。然后添加指向 CustomerListComponent 的路由。

The next step is in customers-routing.module.ts. First, import the component at the top of the file with the other JavaScript import statements. Then, add the route to CustomerListComponent.

import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { CustomerListComponent } from './customer-list/customer-list.component'; const routes: Routes = [ { path: '', component: CustomerListComponent } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class CustomersRoutingModule { }
src/app/customers/customers-routing.module.ts
      
      import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { CustomerListComponent } from './customer-list/customer-list.component';


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

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

注意,path 被设置成了空字符串。这是因为 AppRoutingModule 中的路径已经设置为了 customers,所以 CustomersRoutingModule 中的这个路由定义已经位于 customers 这个上下文中了。也就是说这个路由模块中的每个路由其实都是子路由。

Notice that the path is set to an empty string. This is because the path in AppRoutingModule is already set to customers, so this route in the CustomersRoutingModule, is already within the customers context. Every route in this routing module is a child route.

重复这个步骤以导入 OrdersListComponent,并为 orders-routing.module.ts 配置路由树组:

Repeat this last step of importing the OrdersListComponent and configuring the Routes array for the orders-routing.module.ts:

import { OrderListComponent } from './order-list/order-list.component'; const routes: Routes = [ { path: '', component: OrderListComponent } ];
src/app/orders/orders-routing.module.ts (excerpt)
      
      import { OrderListComponent } from './order-list/order-list.component';

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

现在,如果你在浏览器中查看该应用,这三个按钮会把你带到每个模块去。

Now, if you view the app in the browser, the three buttons take you to each module.

确认它工作正常

Confirm it’s working

你可以使用 Chrome 开发者工具来确认一下这些模块真的是惰性加载的。 在 Chrome 中,按 Cmd+Option+i(Mac)或 Ctrl+Alt+i(PC),并选中 Network 页标签。

You can check to see that a module is indeed being lazy loaded with the Chrome developer tools. In Chrome, open the dev tools by pressing Cmd+Option+i on a Mac or Ctrl+Alt+i on a PC and go to the Network Tab.

lazy loaded modules diagram

点击 Orders 或 Customers 按钮。如果你看到某个 chunk 文件出现了,就表示你已经惰性加载并接入了这个特性模块。Orders 和 Customers 都应该出现一次 chunk,并且它们各自只应该出现一次。

Click on the Orders or Customers button. If you see a chunk appear, you’ve wired everything up properly and the feature module is being lazy loaded. A chunk should appear for Orders and for Customers but will only appear once for each.

lazy loaded modules diagram

要想再次查看它或测试本项目后面的行为,只要点击 Network 页左上放的“清除”图标即可。

To see it again, or to test after working in the project, clear everything out by clicking the circle with a line through it in the upper left of the Network Tab:

lazy loaded modules diagram

然后,使用 Cmd+r(Mac) 或 Ctrl+r(PC) 重新加载页面。

Then reload with Cmd+r or Ctrl+r, depending on your platform.

forRoot()forChild()

forRoot() and forChild()

你可能已经注意到了,CLI 会把 RouterModule.forRoot(routes) 添加到 app-routing.module.tsimports 数组中。 这会让 Angular 知道 AppRoutingModule 是一个路由模块,而 forRoot() 表示这是一个根路由模块。 它会配置你传入的所有路由、让你能访问路由器指令并注册 RouterService。 在 AppRoutingModule 中使用 forRoot(),在本应用中这只会在顶层模块中写一次。

You might have noticed that the CLI adds RouterModule.forRoot(routes) to the app-routing.module.ts imports array. This lets Angular know that this module, AppRoutingModule, is a routing module and forRoot() specifies that this is the root routing module. It configures all the routes you pass to it, gives you access to the router directives, and registers the RouterService. Use forRoot() in the AppRoutingModule—that is, one time in the app at the root level.

CLI 还会把 RouterModule.forChild(routes) 添加到各个特性模块中。这种方式下 Angular 就会知道这个路由列表只负责提供额外的路由并且其设计意图是作为特性模块使用。你可以在多个模块中使用 forChild()

The CLI also adds RouterModule.forChild(routes) to feature routing modules. This way, Angular knows that the route list is only responsible for providing additional routes and is intended for feature modules. You can use forChild() in multiple modules.

forRoot() 包含的注入器配置是全局性的,比如对路由器的配置。forChild() 中没有注入器配置,只有像 RouterOutletRouterLink 这样的指令。

forRoot() contains injector configuration which is global; such as configuring the Router. forChild() has no injector configuration, only directives such as RouterOutlet and RouterLink.


更多关于 NgModule 和路由的知识

More on NgModules and routing

你可能还对下列内容感兴趣:

You may also be interested in the following: