大型網站應用程式專案由於功能較多,如果一開始就把全部的頁面或功能模組引入根模組,使用者體驗會因為初次進入畫面時間過久而降低,為了解決這個問題,延遲載入
模組策略讓使用者不必在第一頁下載整個網站的所有模組、元件原始碼,有效降低初次進入畫面的讀取時間,大幅優化每個功能模組的載入速度。
然而對於網站應用程式中各別的大型功能模組而言,雖然一開始延遲載入降低了初次進入頁面的讀取時間,但每當使用者進入該功能模組的時候,還是可能會產生明顯的卡頓感,為了解決這個問題 Angular 路由模組進一步提供了預先載入
(非同步載入)的機制,當使用者進入網站應用時,預先載入機制會透過非同步背景下載,在不影響畫面顯示或使用者操作的情況下完成功能模組載入。
在 src\app
中得到一個 FeatureModule
ng g m feature
然後建立一些範例頁面元件到 FeatureModule
ng g c hello
ng g c world
src\app\app-routing.module.ts
...
// 路由設定
const routes: Routes = [
{
path: 'home', // 首頁元件路由
component: HomeComponent,
children: [
...
// 延遲載入功能模組
{
path: 'feature',
loadChildren: () => import('./feature/feature.module').then((m) => m.FeatureModule),
},
],
},
...
];
/**
* ## 根模組路由設定
*
* @export
* @class AppRoutingModule
*/
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
src\app\feature\feature.module.ts
...
// 引入功能模組路由設定
import { FeatureRoutingModule } from './feature-routing.module';
/**
* ## 功能模組
*
* @export
* @class FeatureModule
*/
@NgModule({
declarations: [HelloComponent, WorldComponent],
imports: [CommonModule, FeatureRoutingModule, ...NEBULAR_ALL],
})
export class FeatureModule {}
src\app\feature\feature-routing.module.ts
...
import { HelloComponent } from './hello/hello.component';
import { WorldComponent } from './world/world.component';
const FEATURE_ROUTES: Routes = [
{
path: 'hello',
component: HelloComponent,
},
{
path: 'world',
component: WorldComponent,
},
];
/**
* ## 功能模組路由設定
*
* @export
* @class FeatureRoutingModule
*/
@NgModule({
imports: [RouterModule.forChild(FEATURE_ROUTES)],
exports: [RouterModule],
})
export class FeatureRoutingModule {}
src\app\home\home.menuitem.ts
export const NbMenuItems = [
...
{
key: 'FEATURE.TITLE', // 對應 i18n [json檔] 的 key
hidden: false,
title: '功能模組頁面', // 頁面標題
icon: { icon: 'award-outline', pack: 'eva' },
expanded: true, // 預設展開
children: [
{
key: 'FEATURE.HELLO', // 對應 i18n [json檔] 的 key
hidden: false,
title: '哈囉', // 頁面標題
icon: { icon: 'award-outline', pack: 'eva' },
link: '/home/feature/hello', // 路由連結
},
{
key: 'FEATURE.WORLD', // 對應 i18n [json檔] 的 key
hidden: false,
title: '世界', // 頁面標題
icon: { icon: 'award-outline', pack: 'eva' },
link: '/home/feature/world', // 路由連結
},
],
},
];
打包時可在 console 看到結果。
npm run watch
or
ng build --watch --configuration development
延遲載入模組前提是
AppModule
不能跟FeatureModule
有任何相依關係
src\app\app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PreloadAllModules } from '@angular/router'; // 引入預先載入機制模組
...
// 路由設定
const routes: Routes = [
...
];
/**
* ## 根模組路由設定
*
* @export
* @class AppRoutingModule
*/
@NgModule({
imports: [
// 使用預先載入機制模組
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules, // 預先載入全部可被延遲載入的模組
}),
],
exports: [RouterModule],
})
export class AppRoutingModule {}
當有些模組需要預先載入有些不需要的時候,我們也可以自訂 PreloadingStategy
ng g s selected-preloading
建立自訂的預先載入機制 SelectedPreloadingService
src\app\core\services\selected-preloading.service.ts
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
/**
* ## 自訂的預先載入機制
*
* @export
* @class SelectedPreloadingService
* @implements {PreloadingStrategy}
*/
@Injectable({
providedIn: 'root',
})
export class SelectedPreloadingService implements PreloadingStrategy {
preloadedModules: string[] = [];
// route.data.preload 為 true 的模組執行預先載入
preload(route: Route, load: () => Observable<any>): Observable<any> {
if (route.data && route.data['preload']) return load();
else return of(null);
}
}
src\app\app-routing.module.ts
// 引入自訂的預先載入機制
import { SelectedPreloadingService } from '@core/services/selected-preloading.service';
...
// 路由設定
const routes: Routes = [
{
path: 'home', // 首頁元件路由
component: HomeComponent,
children: [
...
// 延遲載入功能模組
{
path: 'feature',
data: { preload: true }, // 設定為 true 時會啟動預先載入機制
loadChildren: () => import('./feature/feature.module').then((m) => m.FeatureModule),
},
],
},
...
];
/**
* ## 根模組路由設定
*
* @export
* @class AppRoutingModule
*/
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: SelectedPreloadingService, // 使用自訂的預先載入機制
}),
],
providers: [SelectedPreloadingService], // 引入自訂的預先載入機制
exports: [RouterModule],
})
export class AppRoutingModule {}
要檢查延遲載入機制或者預先載入機制是否有成功啟動,只能在打包之後到瀏覽器運行中的 Network Panel 進行檢查,運行時打開 F12
,我們看到預先載入的模組的確是 FeatureModule
,如果沒有預先載入則要等到使用者訪問時才會讀取**(延遲載入機制)** FeatureModule
中的元件。
前端架構設計對於預先載入機制 preload
與 prefetch
的名詞會有一些混淆,兩者的作用都在於提早取得將來會用到的資源,差別在於,
今天討論的 Angular 預先載入機制則是偏向於 prefetch 機制。
總結整理 Angular 的 模組載入機制
延遲載入
chunks
。chunks
不會預先載入。預先載入
chunks
。chunks
(或透過設定為可預先載入的模組)會立即預先載入。my-app 還沒有實作登入模組,接下來將帶大家了解 Angular Router Guard
路由守衛
進而完成登入模組。