用Angular2开发一个大型的应用,我们通常都需要分模块进行开发。例如将某一个功能的相关页面和功能放在一个模块里面,这样既可以实现系统的松耦合,给开发和后期的维护带来很大的便利。同时,对于子模块,我们还可以使用延时加载,这样可以减少初始加载的文件的大小。在这篇文章中,我们就来看看在Angular2框架下怎么实现子模块及其延时加载。
可以在这里查看本文使用的实例 。该实例基于上篇文章Angular2使用Guard和Resolve进行验证和权限控制 所用的实例,并在它基础上添加了一个lazy的模块,以及将现有的todo模块配置成延时加载方式。
为了体现启用延时加载前后的包的大小变化,以及启用压缩后的变化,在这个教程里面,使用了angular-cli创建项目脚手架,并用它来进行测试和打包。有关angular-cli的使用请查看 官网 。在这篇文章我们使用的angular-cli的版本是1.0.0-beta.21。如果你使用的是别的版本,可能结果就会不一样。甚至有些错误,我们在最后会说明当前版本angular-cli的bug。
模块设计
在开发Angular2应用时,像组件设计、路由设计以外,对于一个较大型的应用,我们还需要设计模块。例如,将一个应用分成几个功能模块,以及有哪些公用模块。公用模块里面应该放公用的service类,例如权限验证、登录、获取用户信息、全局的错误处理、工具类等,还有封装的指令或组件。而在某一个功能模块里面,只处理这个模块里面的业务,尽量不和其他模块交互。
拿之前教程中的TodoList应用来说,只有home页面和2个todo页面,我们把todo相关的功能放在一个子模块里面,为了演示,又加了一个简单的名字叫lazy的模块。我们将把todo模块和lazy模块配置成延时加载的模块。
子模块开发
接下来再看看子模块的开发。其实在之前的例子中,就把todo相关的组件放在了一个模块里面。但是却没有强调子模块开发需要注意的地方,甚至有些配置可能没有采用子模块的方式进行配置。这里,我们就主要说明一下需要注意的地方,如果要查看完整的代码,请参考 实例源代码 。
子模块路由
首先需要注意的是路由。在之前的例子中,我们把todo相关的路由定义在一个文件中,然后在app的路由定义中把所有路由合并到一起。 todo.routes.ts
的内容如下:
// 省略import export const TodoRoutes: Route[] = [ { path: 'todo', canActivateChild: [MyTodoGuard], children: [ { path: 'list', component: TodoListComponent, resolve: { todos: MyTodoResolver } }, { path: 'detail/:id', component: TodoDetailComponent, canDeactivate: [ CanLeaveTodoDetailGuard ] } ] } ];</div>
然后在 app.routes.ts
中定义一个路由模块:
const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, ...TodoRoutes // 这里就是将TodoRoutes列表里的内容合并到routes ]; @NgModule({ imports: [ RouterModule.forRoot(routes) ], exports: [ RouterModule ] }) export classAppRoutingModule{ }</div>
最后,在AppModule里面引入这个路由模块。
这种方式实现的路由无法实现子模块的延时加载,要实现延时加载,首先要将todo模块的路由修改成子路由模块,也就是要修改 todo.routes.ts
:
// 省略import export const TodoRoutes: Route[] = [ { path: 'todo', canActivateChild: [MyTodoGuard], children: [ { path: 'list', component: TodoListComponent, resolve: { todos: MyTodoResolver } }, { path: 'detail/:id', component: TodoDetailComponent, canDeactivate: [ CanLeaveTodoDetailGuard ] } ] } ]; // 通过下面的方式定义了一个子路由模块 @NgModule({ imports: [ RouterModule.forChild(TodoRoutes) ], exports: [ RouterModule ] }) export classTodoRoutingModule{ }</div>
这里,我们定义了一个子路由模块, TodoRoutingModule
,它使用 RouterModule.forChild(TodoRoutes)
来创建。跟整个App的路由模块比较的话,主路由模块使用 RouterModule.forRoot(routes)
来定义。
定义好了子路由模块,我们就在子模块里面引入它既可:
// 省略import @NgModule({ imports: [CommonModule, FormsModule, TodoRoutingModule ], declarations: [TodoListComponent, TodoDetailComponent, TodoItemComponent], providers: [TodoService, MyTodoResolver, MyTodoGuard, CanLeaveTodoDetailGuard] }) export classTodoModule{}</div>
这样,我们就定义好了一个子模块。当用户打开 /todo/list
或 /todo/detail/*
时,这个子模块里面的相关页面就会展示,它也不会跟其他模块有任何交互。也就是说,进入和离开这个子模块,都是通过路由跳转实现。这个子模块也是完全独立的,可以独立开发,也可以很容易就用到其他应用里面。
延时加载子模块
下面,我们就可以通过修改路由的配置,使得todo模块实现延时加载。Angular的路由模块已经提供了 loadChildren 定义可以直接帮我们实现该功能。下面就是新的app路由定义
const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, { path: 'todo', loadChildren: 'app/todo/todo.module#TodoModule' }, { path: 'lazy', loadChildren: 'app/lazy/lazy.module#LazyModule' } ]; @NgModule({ imports: [ RouterModule.forRoot(routes) ], exports: [ RouterModule ] }) export classAppRoutingModule{ }</div>
在这里,我们对于 todo 路径,交给 app/todo/todo.module
里面的 TodoModule
模块处理。而在 TodoModule
模块里,已经有一个子路由的定义。
最后,再修改 app.module.ts
,保证它里面不再引入 TodoModule
。如此一来,我们在主模块AppModule里面,没有引入 todo
模块的任何组件或服务。这样就能在完全脱离 TodoModule
模块的情况下,运行主模块的功能。当用户打开 /todo
里面的url时,就加载 app/lazy/lazy.module
里面的 LazyModule
模块,并交由它来处理响应的url。
总结一下,实现延时加载子模块,主要是要注意下面几点:
- 子模块的路由用 RouterModule.forChild(TodoRoutes) 方式定义。
- 主模块不要引入子模块,也不要引入子模块的任何组件或服务,否则子模块就会被打包进主模块里。
- 只有子模块才会用到的Service在子模块的 providers 里面定义,如果是主模块和子模块都会用到的Service就用公用模块的方式定义。要注意这个Service的实例只能有一个。
运行
接下来我们来看看运行的结果。(注意根据运行环境不同,文件大小会不一样)
不启用延时加载
首先,我们在 app.module.ts 引入 TodoModule ,这样 todo 模块不是延时加载的,只有 lazy 模块是延时加载的。我们使用 ng serve 的方式运行测试服务器,并打开页面,打开几个页面以后,网络请求如下:
从图中可以看到,有一个3.4M的main的js文件,下面的 1.chunk.js 的 lazy 模块延时加载的。打包的文件确实是非常的大,因为lazy模块非常简单,只是显示了一个字符串在模板里。所以它的大小也非常小,才5.8k。
延时加载模式
下面在把 TodoModule 模块从 app.module.ts 去掉,这样, todo 模块就是延时加载的,再看一下网络请求:
这下main文件变成了3.1M,lazy模块对应的js文件是 1.chunk.js ,还是5.8k,todo模块对应的文件 0.chunk.js 是324K。可以看见一个很简单的todo模块,里面有service, rosolver, guards, 还有3个组件,里面分别都有模块、css,虽然文件不少,但是他们的实现实际上都很小。只是一个模块的文件,在未压缩的情况下就有300多K,让我这个Angular2的忠实粉丝都无语。
延时加载-prod模式
一般我们在部署应用的时候,都会使用压缩、混淆、合并等方法来减少最终文件的大小。使用angular-cli工具,除了在编译的时候提供打包的功能,甚至在测试的时候,也可以启用压缩选项。我们可以运行