Lazy Loading Modules
- 14-01-2024
- Toanngo92
- 0 Comments
Hãy làm một bản ôn tập nhẹ, trong bài trước, chúng ta đã tìm hiểu cách cấu hình một feature module có tên là ArticleModule như sau:
// ArticleModule
@NgModule({
imports: [CommonModule, ArticleRoutingModule],
declarations: [
ArticleComponent,
ArticleListComponent,
ArticleDetailComponent,
],
})
export class ArticleModule {}
Với cấu hình routing trong ArticleRoutingModule như sau:
// ArticleRoutingModule
const routes: Routes = [
{
path: "article",
component: ArticleComponent,
children: [
{
path: "",
component: ArticleListComponent,
},
{
path: ":slug",
component: ArticleDetailComponent,
},
],
},
];
@NgModule({
imports: [CommonModule, RouterModule.forChild(routes)],
declarations: [],
exports: [RouterModule],
})
export class ArticleRoutingModule {}
Cuối cùng, chúng ta import module này vào AppModule:
// AppModule
@NgModule({
imports: [BrowserModule, FormsModule, ArticleModule, AppRoutingModule],
declarations: [AppComponent],
bootstrap: [AppComponent],
})
export class AppModule {}
Sau khi chúng ta chạy lệnh npm run build
để build ra file JS và sử dụng source-map-explorer
để phân tích bundle JS đã được build, bạn có thể mở thư mục dist và kiểm tra bundle main.js bằng cách chạy lệnh source-map-explorer main.js
. Điều quan trọng là khi main.js được load, cả AppModule và ArticleModule đều được load, điều này là như mong đợi của chúng ta.
Sau khi thực hiện lệnh trên, bạn sẽ thấy chi tiết của bundle main.js bao gồm những gì. Tóm tắt là khi main.js được load, cả AppModule và ArticleModule đều được tải. Điều này làm cho chúng ta rất hài lòng.
Mục lục
Thêm một feature module nữa.
Bây giờ chúng ta sẽ thêm một feature module mới là AdminModule, với ý định hiển thị danh sách bài viết dưới dạng bảng và có khả năng chỉnh sửa/xóa bài viết. Cho tiện mục đích minh họa, mình chỉ đặt một nút button mà không thực hiện các chức năng này.
Giao diện sẽ tương tự như sau:
Bây giờ chúng ta bắt đầu với mã nguồn. Mình sẽ tạo một AdminModule gồm hai thành phần chính:
AdminComponent
: Phần layout, tương tự nhưArticleComponent
.AdminArticleListComponent
: Phần hiển thị danh sách bài viết dưới dạng bảng để thực hiện các tác vụ.
Dưới đây là AdminRoutingModule
:
// AdminRoutingModule
const routes: Routes = [
{
path: "admin",
component: AdminComponent,
children: [
{
path: "",
component: AdminArticleListComponent,
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class AdminRoutingModule {}
Tuy nhiên, vì mình sẽ cần render một bảng và sửa Article, nên mình đã import thêm ReactiveFormsModule
trong AdminModule
:
// AdminModule
@NgModule({
declarations: [AdminComponent, AdminArticleListComponent],
imports: [
CommonModule,
ReactiveFormsModule, // <-- Import thêm để sử dụng forms sau này
AdminRoutingModule,
],
})
export class AdminModule {}
Sau đó, mình import AdminRoutingModule
vào AdminModule
và AdminModule
vào AppModule
:
// AppModule
@NgModule({
imports: [BrowserModule, AdminModule, ArticleModule, AppRoutingModule],
declarations: [AppComponent],
bootstrap: [AppComponent],
})
export class AppModule {}
Khi chúng ta chạy npm start
, bạn sẽ thấy giao diện như trong hình động ở trên. Mình sẽ không hiển thị lại ở đây nữa. Phần thú vị bây giờ liên quan đến JS Bundle. Tiếp tục sử dụng source-map-explorer
để kiểm tra file main.js, bạn sẽ thấy AdminModule
đã được thêm vào trong bundle. Vậy còn Angular Core và ReactiveFormsModule
thì ở đâu? Đáp án là chúng được đặt trong một file có tên là vendor.js. Hãy kiểm tra file này bằng cách chạy lệnh source-map-explorer vendor.js
và bạn sẽ nhận được kết quả như sau:
Các module như Forms nằm ở đây, được đánh dấu bằng phần bôi đỏ.
Tóm lại, nếu bạn sử dụng Feature Module, khi ứng dụng được tải, tất cả thư viện và component bạn sử dụng trong TẤT CẢ các Feature Module sẽ được Angular tải. Đối với ứng dụng nhỏ, điều này có lẽ không quá ảnh hưởng, nhưng với ứng dụng lớn, chắc chắn sẽ tác động đáng kể đến trải nghiệm người dùng. Giờ, chúng ta sẽ chuyển sang phần quan trọng của bài viết – Lazy Load Module. Khi đã áp dụng kỹ thuật này, AdminModule
và ReactiveFormsModule
sẽ chỉ được tải khi bạn mở URL /admin
.
Lazy load module
Để hỗ trợ lazy loading module trong Angular, trước tiên, chúng ta cần thực hiện một số bước.
Bước 1: Xóa AdminModule
khỏi mảng import của AppModule
. Việc này giúp tránh tình trạng tải toàn bộ mã nguồn của AdminModule
cùng với AppModule
, như đã thấy ở trên. Đồng thời, nhớ xóa cả phần import liên quan ở trên nữa.
// AppModule
@NgModule({
imports: [BrowserModule, ArticleModule, AppRoutingModule],
declarations: [AppComponent],
bootstrap: [AppComponent],
})
export class AppModule {}
Bước 2: Cấu hình lại route cho AdminModule
. Loại bỏ phần path admin
trong AdminRoutingModule
. Lý do cho bước này có thể xem ở bước 3.
// AdminRoutingModule
const routes: Routes = [
{
path: "",
component: AdminComponent,
children: [
{
path: "",
component: AdminArticleListComponent,
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class AdminRoutingModule {}
Bước 3: Trong AppRoutingModule
, chúng ta cần cấu hình để load AdminModule
theo cú pháp lazy loading module khi mở ứng dụng ở path có dạng /admin
.
// AppRoutingModule
const routes: Routes = [
{
path: "admin",
loadChildren: () => import('./admin/admin.module').then((m) => m.AdminModule),
},
// ...other routes
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
Lưu ý rằng cú pháp lazy-loading sử dụng loadChildren
tiếp theo là một hàm sử dụng cú pháp import('...')
tích hợp trong trình duyệt để tải module động. Đường dẫn import
là đường dẫn tương đối đến module.
Sau khi thực hiện bước này, chúng ta chạy npm run build
một lần nữa. CLI sẽ tạo ra một file JS mới được dành riêng cho AdminModule
có tên là admin-admin-module.js
.
Bước 4: Mở ứng dụng để kiểm tra. Bạn sẽ thấy một sự khác biệt lớn.
Khi tải trang lần đầu, ứng dụng sẽ chỉ tải các file cần thiết và main.js
. Tuy nhiên, khi bạn nhấp vào Admin
để chuyển đến /admin
, lúc đó mới có tải admin-admin-module.js
. Lazy load chính là điều này, tức là không tải ngay từ đầu, mà chỉ tải khi cần.
Bước 5: Chúng ta cùng xem phân tích của main.js
và admin-admin-module.js
.
source-map-explorer main.js
source-map-explorer admin-admin-module.js
Bundle của main.js
giờ không chứa AdminModule
nữa, chỉ còn ArticleModule
.
Đây là phân tích của admin-admin-module.js
. Phần import của Form
và các component của AdminModule
đã nằm trong bundle này. Rất xuất sắc.
Vậy là đã xong. Lazy load module giúp chúng ta tối ưu hóa việc tải module chỉ khi cần thiết, giúp ứng dụng hoạt động hiệu quả hơn.
Lazy load syntax
Cú pháp import('...')
được khuyến khích sử dụng từ phiên bản Angular 8 trở đi. Trước đó, từ phiên bản Angular 7 và thấp hơn, có một cách sử dụng khác như sau: loadChildren: './admin/admin.module#AdminModule'
. Đây là một chuỗi “magic”, chỉ định đường dẫn tệp đến tệp chứa NgModule cùng với Router cần tải.
Preloading Lazy Module
Việc chia thành các lazy loading module mang lại nhiều lợi ích cho trải nghiệm của người dùng khi tải trang. Với việc giảm kích thước file JS cần phải tải, thời gian chờ đợi để tương tác với trang web giảm đi. Tuy nhiên, đôi khi các lazy loading module có thể có kích thước lớn, dẫn đến việc mất thời gian khi người dùng chuyển đến một trang mới. Ví dụ như việc lazy loading AdminModule, giả sử mạng của người dùng giả định là mạng di động, hãy xem tình hình thế nào.
![Bước 13] – Thời gian chờ khi tải AdminModule Người dùng phải đợi lâu, có thể lên đến vài giây từ khi bấm vào liên kết Admin cho đến khi giao diện người dùng hiện ra. Quá trình này quá chậm.
Để giải quyết vấn đề này, chúng ta có thể sử dụng kỹ thuật “Preloading” để tải các bundle trong nền.
Để kích hoạt preloading cho tất cả các lazy loaded modules, bạn cần import PreloadAllModules
từ gói @angular/router
và cấu hình nó trong AppRoutingModule
, trong phần forRoot
.
import { PreloadAllModules } from "@angular/router";
const routes: Routes = [
{
path: "admin",
loadChildren: () =>
import("./admin/admin.module").then((m) => m.AdminModule),
},
{
path: "",
redirectTo: "article",
pathMatch: "full",
},
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules,
}),
],
exports: [RouterModule],
})
export class AppRoutingModule {}
Chạy lại ứng dụng để kiểm tra.
![Bước 14] – Kết quả khi sử dụng Preloading Tuyệt vời! Nếu bạn quay trở lại trang chủ, bạn sẽ thấy rằng ngay sau khi trang chủ được tải, ứng dụng đã tự động tải về admin-admin-module.js
. Do mình không sử dụng lệnh ng build
với cờ --prod=true
, nên kích thước file hơi lớn. Tuy nhiên, khi bạn xây dựng với cờ --prod
, kích thước file sẽ nhẹ hơn nhiều.
Để preload các module theo ý muốn thay vì toàn bộ các lazy loading module, bạn có thể tìm hiểu thêm tại đây. Tuy bài viết có giới hạn, nhưng mình hy vọng rằng nó sẽ giúp bạn hiểu rõ hơn về cách sử dụng kỹ thuật Preloading trong Angular.