淺析Angular2子模塊以及異步加載
用Angular2開發(fā)一個大型的應(yīng)用,我們通常都需要分模塊進行開發(fā)。例如將某一個功能的相關(guān)頁面和功能放在一個模塊里面,這樣既可以實現(xiàn)系統(tǒng)的松耦合,給開發(fā)和后期的維護帶來很大的便利。同時,對于子模塊,我們還可以使用延時加載,這樣可以減少初始加載的文件的大小。在這篇文章中,我們就來看看在Angular2框架下怎么實現(xiàn)子模塊及其延時加載。
可以在這里查看本文使用的實例 。該實例基于上篇文章Angular2使用Guard和Resolve進行驗證和權(quán)限控制 所用的實例,并在它基礎(chǔ)上添加了一個lazy的模塊,以及將現(xiàn)有的todo模塊配置成延時加載方式。
為了體現(xiàn)啟用延時加載前后的包的大小變化,以及啟用壓縮后的變化,在這個教程里面,使用了angular-cli創(chuàng)建項目腳手架,并用它來進行測試和打包。有關(guān)angular-cli的使用請查看 官網(wǎng) 。在這篇文章我們使用的angular-cli的版本是1.0.0-beta.21。如果你使用的是別的版本,可能結(jié)果就會不一樣。甚至有些錯誤,我們在最后會說明當(dāng)前版本angular-cli的bug。
模塊設(shè)計
在開發(fā)Angular2應(yīng)用時,像組件設(shè)計、路由設(shè)計以外,對于一個較大型的應(yīng)用,我們還需要設(shè)計模塊。例如,將一個應(yīng)用分成幾個功能模塊,以及有哪些公用模塊。公用模塊里面應(yīng)該放公用的service類,例如權(quán)限驗證、登錄、獲取用戶信息、全局的錯誤處理、工具類等,還有封裝的指令或組件。而在某一個功能模塊里面,只處理這個模塊里面的業(yè)務(wù),盡量不和其他模塊交互。
拿之前教程中的TodoList應(yīng)用來說,只有home頁面和2個todo頁面,我們把todo相關(guān)的功能放在一個子模塊里面,為了演示,又加了一個簡單的名字叫l(wèi)azy的模塊。我們將把todo模塊和lazy模塊配置成延時加載的模塊。
子模塊開發(fā)
接下來再看看子模塊的開發(fā)。其實在之前的例子中,就把todo相關(guān)的組件放在了一個模塊里面。但是卻沒有強調(diào)子模塊開發(fā)需要注意的地方,甚至有些配置可能沒有采用子模塊的方式進行配置。這里,我們就主要說明一下需要注意的地方,如果要查看完整的代碼,請參考 實例源代碼 。
子模塊路由
首先需要注意的是路由。在之前的例子中,我們把todo相關(guān)的路由定義在一個文件中,然后在app的路由定義中把所有路由合并到一起。 todo.routes.ts
的內(nèi)容如下:
// 省略import export const TodoRoutes: Route[] = [ { path: 'todo', canActivateChild: [MyTodoGuard], children: [ { path: 'list', component: TodoListComponent, resolve: { todos: MyTodoResolver } }, { path: 'detail/:id', component: TodoDetailComponent, canDeactivate: [ CanLeaveTodoDetailGuard ] } ] } ];
然后在 app.routes.ts
中定義一個路由模塊:
const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, ...TodoRoutes // 這里就是將TodoRoutes列表里的內(nèi)容合并到routes ]; @NgModule({ imports: [ RouterModule.forRoot(routes) ], exports: [ RouterModule ] }) export classAppRoutingModule{ }
最后,在AppModule里面引入這個路由模塊。
這種方式實現(xiàn)的路由無法實現(xiàn)子模塊的延時加載,要實現(xiàn)延時加載,首先要將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{ }
這里,我們定義了一個子路由模塊, TodoRoutingModule
,它使用 RouterModule.forChild(TodoRoutes)
來創(chuàng)建。跟整個App的路由模塊比較的話,主路由模塊使用 RouterModule.forRoot(routes)
來定義。
定義好了子路由模塊,我們就在子模塊里面引入它既可:
// 省略import @NgModule({ imports: [CommonModule, FormsModule, TodoRoutingModule ], declarations: [TodoListComponent, TodoDetailComponent, TodoItemComponent], providers: [TodoService, MyTodoResolver, MyTodoGuard, CanLeaveTodoDetailGuard] }) export classTodoModule{}
這樣,我們就定義好了一個子模塊。當(dāng)用戶打開 /todo/list
或 /todo/detail/*
時,這個子模塊里面的相關(guān)頁面就會展示,它也不會跟其他模塊有任何交互。也就是說,進入和離開這個子模塊,都是通過路由跳轉(zhuǎn)實現(xiàn)。這個子模塊也是完全獨立的,可以獨立開發(fā),也可以很容易就用到其他應(yīng)用里面。
延時加載子模塊
下面,我們就可以通過修改路由的配置,使得todo模塊實現(xiàn)延時加載。Angular的路由模塊已經(jīng)提供了 loadChildren 定義可以直接幫我們實現(xiàn)該功能。下面就是新的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{ }
在這里,我們對于 todo 路徑,交給 app/todo/todo.module
里面的 TodoModule
模塊處理。而在 TodoModule
模塊里,已經(jīng)有一個子路由的定義。
最后,再修改 app.module.ts
,保證它里面不再引入 TodoModule
。如此一來,我們在主模塊AppModule里面,沒有引入 todo
模塊的任何組件或服務(wù)。這樣就能在完全脫離 TodoModule
模塊的情況下,運行主模塊的功能。當(dāng)用戶打開 /todo
里面的url時,就加載 app/lazy/lazy.module
里面的 LazyModule
模塊,并交由它來處理響應(yīng)的url。
總結(jié)一下,實現(xiàn)延時加載子模塊,主要是要注意下面幾點:
- 子模塊的路由用 RouterModule.forChild(TodoRoutes) 方式定義。
- 主模塊不要引入子模塊,也不要引入子模塊的任何組件或服務(wù),否則子模塊就會被打包進主模塊里。
- 只有子模塊才會用到的Service在子模塊的 providers 里面定義,如果是主模塊和子模塊都會用到的Service就用公用模塊的方式定義。要注意這個Service的實例只能有一個。
運行
接下來我們來看看運行的結(jié)果。(注意根據(jù)運行環(huán)境不同,文件大小會不一樣)
不啟用延時加載
首先,我們在 app.module.ts 引入 TodoModule ,這樣 todo 模塊不是延時加載的,只有 lazy 模塊是延時加載的。我們使用 ng serve 的方式運行測試服務(wù)器,并打開頁面,打開幾個頁面以后,網(wǎng)絡(luò)請求如下:
從圖中可以看到,有一個3.4M的main的js文件,下面的 1.chunk.js 的 lazy 模塊延時加載的。打包的文件確實是非常的大,因為lazy模塊非常簡單,只是顯示了一個字符串在模板里。所以它的大小也非常小,才5.8k。
延時加載模式
下面在把 TodoModule 模塊從 app.module.ts 去掉,這樣, todo 模塊就是延時加載的,再看一下網(wǎng)絡(luò)請求:
這下main文件變成了3.1M,lazy模塊對應(yīng)的js文件是 1.chunk.js ,還是5.8k,todo模塊對應(yīng)的文件 0.chunk.js 是324K。可以看見一個很簡單的todo模塊,里面有service, rosolver, guards, 還有3個組件,里面分別都有模塊、css,雖然文件不少,但是他們的實現(xiàn)實際上都很小。只是一個模塊的文件,在未壓縮的情況下就有300多K,讓我這個Angular2的忠實粉絲都無語。
延時加載-prod模式
一般我們在部署應(yīng)用的時候,都會使用壓縮、混淆、合并等方法來減少最終文件的大小。使用angular-cli工具,除了在編譯的時候提供打包的功能,甚至在測試的時候,也可以啟用壓縮選項。我們可以運行 ng serve -pro
來使用 prod
模式來啟動測試服務(wù)器。在啟動的過程中,可以看到很多類似下面的日志:
WARNING in 0.005fea95566fdabe23df.chunk.js from UglifyJs Dropping unused function scheduleMicroTask [/Users/mavlarn/mydev/blog/angular2-tutorial/angular2-routes-lazy-module-webpack/~/@angular/forms/src/facade/lang.js:21,0]
可以看出,angular-cli的 prod 模式下編譯的時候,去除了很多不需要的代碼,這就是angular的 Tree Shaking 的功能。
運行以后,網(wǎng)絡(luò)請求如下:
這下main文件減少到了221K,lazy模塊對應(yīng)的js文件是 1.chunk.js ,只有1.0k,todo模塊對應(yīng)的文件 0.chunk.js 是17.9K。總共大小大概是240K左右,如果再使用GZip壓縮,應(yīng)該可以到6,70K左右。在官方文檔里提到,一個Angular2的簡單實例,通過Tree Shaking、壓縮、GZip,最終下載的包大小有50K。我們這個實例畢竟稍微復(fù)雜,實現(xiàn)了大多數(shù)的通用功能,如路由、guard、resolver、表單,也是用到了Rxjs里的 Observable ,所以最終壓縮后能有70K左右的話,也符合官方文檔的說法。
編譯后
最后,我們再使用 ng build --prod
來看看用prod模式編譯后的大?。?/p>
結(jié)果出乎意外,main文件的大小比上面在prod模式下運行測試服務(wù)器大很多,達到800多K。應(yīng)該是編譯過程需要某些參數(shù),或者是當(dāng)前的angular-cli有什么bug。
再使用 ng build --prod --aot
編譯,main文件的大小是446K。雖然小了一點,但是也不符合預(yù)期。
總結(jié)
先說延時加載,應(yīng)該都知道可以減少第一次加載的文件的大小。特別是當(dāng)某個模塊使用了一些比較大第三方的js庫,例如圖形庫等,那么,把這些模塊獨立出來,使用延時加載的方式,可以大大減少首次加載的時間。對于Angular2的應(yīng)用來說,如果我們要定義 Component ,就從 @angular/core 里面引入 Component ,需要定義路由就從 @angular/router 里面引入`Router。所以,只要我們設(shè)計好了整個App的模塊、組件、路由,我們就可以利用延時加載的功能使得首頁文件盡可能的小。
使用模塊化的開發(fā),也能給我們的開發(fā)和維護帶來很大的便利,項目越大越大,模塊化和組件化帶來的便利就越明顯。
目前Angular2的天坑
在網(wǎng)上,經(jīng)??梢钥吹揭恍┪恼抡fAngular1或者2的一些坑。實際上,大部分都是因為使用不當(dāng),或者沒有按照最佳實踐去使用,特別是Angular1。雖然Angular1有本質(zhì)上的性能問題,但是,通過良好的整體設(shè)計、良好的 代碼規(guī)范和質(zhì)量,還是可以開發(fā)出很流暢的手機web應(yīng)用。
但是,在準備這篇文章中的實例時,卻遇到了幾個嚴重的問題,讓我這個Angular2的忠實粉絲也很無奈。
Angular 2.2.2及以上版本的BUG
我在實例中使用的Angular的版本是2.2.1,如果用的版本是2.2.2 ~ 2.3.0之間,在運行或編譯的時候,可能會出現(xiàn)如下的錯誤:
ngCompiler.ReflectorHost is not a constructor TypeError: ngCompiler.ReflectorHost is not a constructor
可以上Github查看該 issue 的情況。如果遇到這種問題,只能先使用2.2.1的版本。
Angular-Router
在這個實例中,延時加載的todo模塊里面有一個service,我們使用Angular的依賴注入的功能自動初始化以及諸如這個服務(wù)的實例。但是,在3.1.2及以上的版本里面,這個服務(wù)會被創(chuàng)建多次,每次激活相關(guān)路由的時候,就會創(chuàng)建一次。而且,只有在延時加載的模式才會發(fā)生這種錯誤。相關(guān) issue
TypeScript
在我之前的教程里,判斷用戶是否具有某種權(quán)限,使用了如下的方法:
hasRole(role: string): boolean { return this.account && this.account.roles.includes(role); }
但是,更新了TypeScript以后,該方法就不存在了,原因可以查看 這個 .
所以改成了用 indexOf(role) > 0 來判斷列表里是否存在一個字符串。
雖然目前Angular還不是十分穩(wěn)定,有一些Bug,甚至TypeScript也不穩(wěn)定,但是,相信這些問題都能夠很快解決。而且隨著框架越來越成熟,也會越來越穩(wěn)定。
而且,Angular2+Typescript的開發(fā)方式也十分便利,Typescript的強類型檢查能夠幫助我們減少編碼的錯誤,提高效率。而且,我們也可以很方便的查看框架的API,能省去很多查資料的時間。
Angular2的很多思想非常適用于開發(fā)大型的應(yīng)用。如果開發(fā)過大型的Java項目,就會發(fā)現(xiàn)學(xué)習(xí)Angular2是一件非常容易的事情。Angular2引入了很多面向?qū)ο蟮目蚣艿乃枷?,而這些,都是在面向?qū)ο箢I(lǐng)域開發(fā)大型項目的多年開發(fā)經(jīng)驗。這些經(jīng)驗應(yīng)用到前端開發(fā),也能幫助我們更方便的開發(fā)和維護大型的前端項目。
雖然,Angular2的應(yīng)用最終的打包文件非常大(我們這個實例即使壓縮完后也有70K左右,但是如果用VUE的話會比這個小很多),但是隨著Angular2的越來越穩(wěn)定,各種開發(fā)工具越來越成熟,相信文件大小的問題也能夠有一個比較好的解決方案。因為Angular2的AOT、Tree Shaking的特性,為解決大小的問題提供了前提。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
AngularJS基于factory創(chuàng)建自定義服務(wù)的方法詳解
這篇文章主要介紹了AngularJS基于factory創(chuàng)建自定義服務(wù)的方法,結(jié)合實例形式分析了AngularJS使用factory創(chuàng)建自定義服務(wù)的具體步驟、操作技巧與相關(guān)注意事項,需要的朋友可以參考下2017-05-05AngularJs入門教程之環(huán)境搭建+創(chuàng)建應(yīng)用示例
這篇文章主要介紹了AngularJs入門教程之環(huán)境搭建+創(chuàng)建應(yīng)用的方法,較為詳細的分析了AngularJS的功能、下載及應(yīng)用創(chuàng)建方法,需要的朋友可以參考下2016-11-11AngularJS之頁面跳轉(zhuǎn)Route實例代碼
本篇文章主要介紹了AngularJS之頁面跳轉(zhuǎn)Route ,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03angularjs $http實現(xiàn)form表單提交示例
這篇文章主要介紹了angularjs $http實現(xiàn)form表單提交示例,非常具有實用價值,需要的朋友可以參考下2017-06-06Angular 4環(huán)境準備與Angular cli創(chuàng)建項目詳解
Angular4.0來了,更小,更快,改動少,所以下面這篇文章主要給大家介紹了關(guān)于Angular 4環(huán)境準備與學(xué)會使用Angular cli創(chuàng)建項目的相關(guān)資料,文中給出了詳細的示例代碼供大家參考學(xué)習(xí),需要的朋友們下面來一起看看吧。2017-05-05AngularJS中取消對HTML片段轉(zhuǎn)義的方法例子
這篇文章主要介紹了AngularJS中取消對HTML片段轉(zhuǎn)義的方法例子,在一些需要顯示HTML的地方,就要取消AngularJS的轉(zhuǎn)義,本文就介紹了這種方法,需要的朋友可以參考下2015-01-01