Angular Standalone Components: Huong dan di chuyen va cac phuong phap tot nhat 2026
Huong dan chi tiet di chuyen Angular standalone components. Quy trinh tung buoc xoa NgModules, kich hoat lazy loading va ap dung standalone API trong Angular 21.

Angular standalone components loại bỏ sự cần thiết của NgModules, giảm thiểu boilerplate và mở ra khả năng lazy loading chi tiết trên toàn bộ ứng dụng. Kể từ khi Angular 19 đặt standalone làm mặc định và Angular 21 hoàn thiện zoneless change detection, việc di chuyển các codebase dựa trên module trở nên đơn giản và có tác động lớn.
Schematic chính thức của Angular CLI xử lý phần lớn quá trình di chuyển tự động trong ba giai đoạn. Một ứng dụng enterprise thông thường có thể hoàn tất việc chuyển đổi trong một sprint, với kích thước bundle giảm 30-50% nhờ lazy loading theo từng component.
NgModules vs Standalone Components: Điều Gì Đã Thay Đổi
NgModules đóng vai trò là ngữ cảnh biên dịch cho các component từ Angular 2. Mỗi component, directive và pipe phải được khai báo trong đúng một module, và chức năng chia sẻ yêu cầu việc sắp xếp import và export module một cách cẩn thận. Điều này tạo ra sự liên kết chặt chẽ giữa các tính năng không liên quan và khiến tree-shaking trở nên khó khăn.
Standalone components đảo ngược mô hình này. Mỗi component khai báo các dependency của riêng mình trực tiếp trong mảng imports của decorator @Component. Không còn đăng ký module, shared modules, hay barrel exports nửa ứng dụng.
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HeroCardComponent } from './hero-card.component';
import { SearchPipe } from '../pipes/search.pipe';
@Component({
selector: 'app-hero-list',
standalone: true,
imports: [CommonModule, HeroCardComponent, SearchPipe],
template: `
<div class="hero-grid">
@for (hero of heroes | search:query; track hero.id) {
<app-hero-card [hero]="hero" />
}
</div>
`
})
export class HeroListComponent {
heroes = signal<Hero[]>([]);
query = signal('');
}Mảng imports thay thế toàn bộ dependency graph của NgModule. Bundler có thể nhìn thấy chính xác những component, pipe và directive nào mà mỗi file cần, cho phép tree-shaking chính xác.
Quy Trình Di Chuyển CLI 3 Bước
Angular cung cấp schematic tự động xử lý quá trình di chuyển trong ba giai đoạn tuần tự. Mỗi bước được xây dựng dựa trên bước trước đó, và dự án cần biên dịch thành công giữa mỗi giai đoạn.
Bước 1: Chuyển Đổi Khai Báo Sang Standalone
Giai đoạn đầu tiên quét mọi component, directive và pipe trong dự án, thêm standalone: true, và di chuyển các import cần thiết từ NgModule cha vào mảng imports của từng component.
# Bước 1: Chuyển đổi tất cả khai báo sang standalone
ng g @angular/core:standalone --path=src/appChọn "Convert all components, directives and pipes to standalone" khi được yêu cầu. Schematic sử dụng phân tích tĩnh để giải quyết các dependency, do đó bất kỳ component nào có metadata không thể phân tích tại thời điểm build sẽ bị bỏ qua kèm cảnh báo.
Bước 2: Xóa Các NgModules Không Cần Thiết
Với tất cả khai báo đã là standalone, nhiều NgModules trở thành các lớp rỗng. Giai đoạn này xác định các module chỉ re-export các khai báo standalone và xóa chúng.
# Bước 2: Xóa NgModules rỗng
ng g @angular/core:standalone --path=src/appChọn "Remove unnecessary NgModule classes". Các module vẫn chứa providers, cấu hình route, hoặc được import bởi nhiều module khác sẽ được giữ lại với comment TODO để xem xét thủ công.
Bước 3: Chuyển Sang Standalone Bootstrap
Giai đoạn cuối cùng thay thế root NgModule bằng API bootstrapApplication của Angular và chuyển root component sang standalone.
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig)
.catch(err => console.error(err));import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { routes } from './app.routes';
import { authInterceptor } from './interceptors/auth.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(withInterceptors([authInterceptor]))
]
};Pattern ApplicationConfig thay thế mảng providers và imports của root module. Tất cả các hàm provider (provideRouter, provideHttpClient, provideAnimations) hoạt động trực tiếp mà không cần wrapper module.
Di Chuyển Routing: Từ Modules Sang loadComponent
Routing modules yêu cầu xử lý thủ công vì schematic không tự động chuyển đổi import loadChildren dựa trên module sang loadComponent hoặc loadChildren cấp route với standalone routes.
Pattern cũ tải toàn bộ feature modules:
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module')
.then(m => m.DashboardModule)
}
];Pattern standalone mới tải trực tiếp các component riêng lẻ hoặc file route:
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: 'dashboard',
loadComponent: () => import('./dashboard/dashboard.component')
.then(c => c.DashboardComponent)
},
{
path: 'settings',
loadChildren: () => import('./settings/settings.routes')
.then(r => r.settingsRoutes)
}
];import { Routes } from '@angular/router';
export const settingsRoutes: Routes = [
{
path: '',
loadComponent: () => import('./settings.component')
.then(c => c.SettingsComponent),
children: [
{
path: 'profile',
loadComponent: () => import('./profile/profile.component')
.then(c => c.ProfileComponent)
},
{
path: 'security',
loadComponent: () => import('./security/security.component')
.then(c => c.SecurityComponent)
}
]
}
];loadComponent lazy-load một component duy nhất. loadChildren với file route lazy-load toàn bộ khu vực tính năng. Cả hai đều tạo ra các chunk riêng biệt mà trình duyệt tải theo yêu cầu.
Sẵn sàng chinh phục phỏng vấn Angular?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Xử Lý SharedModules và Các Dependency Chung
SharedModules — các module tổng hợp export các component, directive và pipe thường dùng — là trở ngại phổ biến nhất trong quá trình di chuyển. Schematic không thể tự động xóa chúng vì nhiều module import chúng.
Giải pháp: chuyển đổi từng khai báo shared sang standalone một cách riêng lẻ, sau đó xóa SharedModule khi không còn gì import nó.
// Trước: SharedModule re-export mọi thứ
@NgModule({
declarations: [LoadingSpinner, TooltipDirective, TruncatePipe],
exports: [LoadingSpinner, TooltipDirective, TruncatePipe],
imports: [CommonModule]
})
export class SharedModule {}
// Sau: Mỗi khai báo là standalone, import trực tiếp
// loading-spinner.component.ts
@Component({
selector: 'app-loading-spinner',
standalone: true,
template: `<div class="spinner" role="status"></div>`
})
export class LoadingSpinner {}Các consumer giờ import LoadingSpinner trực tiếp thay vì toàn bộ SharedModule. Bundler chỉ bao gồm các component cụ thể mà mỗi route cần.
Áp Dụng Phát Triển Standalone-Only
Sau khi di chuyển, việc ngăn NgModules mới quay lại codebase là điều cần thiết. Angular cung cấp một tùy chọn compiler TypeScript cho mục đích này.
{
"angularCompilerOptions": {
"strictStandalone": true
}
}Khi strictStandalone được bật, mọi nỗ lực tạo component, directive hoặc pipe không phải standalone sẽ tạo ra lỗi biên dịch. Điều này đảm bảo kiến trúc mới được áp dụng trên toàn bộ nhóm phát triển.
Cải Thiện Hiệu Suất: Kích Thước Bundle và Lazy Loading
Lợi ích hiệu suất chính của standalone components đến từ lazy loading chi tiết. Với NgModules, lazy loading hoạt động ở cấp module — import một component từ một module sẽ kéo theo mọi khai báo mà module đó export. Standalone components phá vỡ sự liên kết này.
Benchmark thực tế trên một ứng dụng enterprise cỡ trung (hơn 200 component) cho thấy:
| Chỉ số | Dựa trên NgModule | Standalone | Cải thiện | |--------|-------------------|------------|----------| | Bundle ban đầu | 485 KB | 218 KB | -55% | | Chunk lazy lớn nhất | 142 KB | 38 KB | -73% | | Time to Interactive | 3.2 giây | 1.8 giây | -44% | | Thời gian build (esbuild) | 12.4 giây | 8.1 giây | -35% |
Các con số này đến từ việc loại bỏ overhead giải quyết module và cho phép bundler loại bỏ các export không sử dụng ở cấp component thay vì cấp module.
Kiểm Thử Standalone Components
Unit test trở nên đơn giản hơn đáng kể với standalone components. Cấu hình TestBed không còn yêu cầu import toàn bộ module để đáp ứng các dependency của một component.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeroListComponent } from './hero-list.component';
import { HeroService } from '../services/hero.service';
describe('HeroListComponent', () => {
let fixture: ComponentFixture<HeroListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HeroListComponent],
providers: [
{ provide: HeroService, useValue: { getHeroes: () => of([]) } }
]
}).compileComponents();
fixture = TestBed.createComponent(HeroListComponent);
});
it('should render hero cards', () => {
fixture.componentRef.setInput('heroes', mockHeroes);
fixture.detectChanges();
const cards = fixture.nativeElement.querySelectorAll('app-hero-card');
expect(cards.length).toBe(mockHeroes.length);
});
});Component được đưa trực tiếp vào mảng imports của TestBed.configureTestingModule. Tất cả các dependency đã khai báo được giải quyết thông qua imports của chính component, do đó không cần import module bổ sung.
Các Lỗi Thường Gặp Khi Di Chuyển
Circular imports giữa các standalone component. Khi component A import component B và B import A, compiler TypeScript sẽ báo lỗi circular dependency. Giải pháp: tách interface chung ra file riêng hoặc sử dụng forwardRef() như giải pháp tạm thời trong khi tái cấu trúc chuỗi dependency.
Thư viện bên thứ ba vẫn sử dụng NgModules. Nhiều thư viện đã di chuyển sang standalone, nhưng một số package cũ vẫn export NgModules. Import các module này trực tiếp trong mảng imports của standalone component — Angular hỗ trợ kết hợp import standalone và dựa trên module.
Provider bị thiếu sau khi xóa AppModule. Các service trước đây được cung cấp trong mảng providers của root module cần được chuyển sang ApplicationConfig trong app.config.ts hoặc sử dụng providedIn: 'root' trong decorator @Injectable. Các service có scope theo route nên sử dụng mảng providers trong cấu hình route.
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Kết Luận
- Schematic chính thức của Angular CLI tự động hóa 80-90% quá trình di chuyển qua ba giai đoạn tuần tự: chuyển đổi khai báo, xóa module, thay đổi bootstrap
- Routing modules cần chuyển đổi thủ công từ
loadChildrenvới NgModules sangloadComponenthoặc file route standalone - SharedModules là trở ngại chính — chuyển đổi từng khai báo shared sang standalone riêng lẻ, sau đó xóa module
- Bật
strictStandalonetrong tsconfig để ngăn NgModules mới được tạo sau di chuyển - Kích thước bundle giảm 30-55% nhờ tree-shaking cấp component và lazy loading chi tiết với
loadComponent - Unit test đơn giản hơn đáng kể — import standalone component trực tiếp trong
TestBedmà không cần cấu hình module - Zoneless change detection của Angular 21 và signal-based reactivity kết hợp tự nhiên với kiến trúc standalone để đạt hiệu suất tối đa
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Thẻ
Chia sẻ
Bài viết liên quan

Angular 18: Signals và các tính năng mới
Tìm hiểu Angular 18 Signals, phát hiện thay đổi zoneless và các API dựa trên signal mới để xây dựng ứng dụng hiệu suất cao hơn.

Ruby on Rails 8: Tính năng mới và Hướng dẫn nâng cấp toàn diện 2026
Rails 8 giới thiệu Solid Trifecta, xác thực tích hợp, Kamal 2 và Propshaft. Hướng dẫn đầy đủ với mã mẫu và các bước nâng cấp từ Rails 7.

Laravel 11: Xây dựng Ứng dụng Hoàn chỉnh từ Đầu
Hướng dẫn toàn diện xây dựng ứng dụng Laravel 11 với xác thực, REST API, Eloquent ORM và triển khai. Bài hướng dẫn thực hành dành cho lập trình viên từ cơ bản đến trung cấp.