Angular v14+ Standalone Components:告別 NgModule,擁抱更簡潔的元件架構
在 Angular 的傳統架構中,NgModule
扮演著組織應用程式、封裝元件 (Components)、指令 (Directives) 和管道 (Pipes) 的核心角色。然而,隨著應用程式規模的增長,管理眾多的 NgModule
有時會變得複雜,並可能引入不必要的樣板程式碼。
為了解決這些問題並簡化 Angular 的開發模型,Angular v14 引入了 Standalone Components (獨立元件),並在 v15 正式穩定。這個特性允許開發者建立不依賴於任何 NgModule
的元件、指令和管道,使得架構更輕量、依賴關係更清晰,並且是目前 Angular 官方推薦的元件開發方式。
直觀比較:Standalone Components 如何簡化元件定義
讓我們以一個簡單的「使用者歡迎卡片」元件為例,看看 Standalone Components 與傳統 NgModule-based Components 在定義上有何不同。
過去寫法 (NgModule-based Component):
通常需要至少兩個檔案:元件本身的 .ts
檔案和一個專門用來聲明與導出此元件的 .module.ts
檔案 (或者在一個共享模組中聲明)。
<!-- user-card.component.ts (舊版) -->
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-user-card-old',
template: `<div>歡迎,{{ userName }}!</div>`
})
export class UserCardOldComponent {
@Input() userName: string = '';
}
<!-- user-card.module.ts (舊版) -->
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; // 可能需要,如果模板用了 *ngIf 等
import { UserCardOldComponent } from './user-card.component';
@NgModule({
declarations: [UserCardOldComponent],
imports: [CommonModule],
exports: [UserCardOldComponent] // 如果其他模組要使用
})
export class UserCardOldModule {}
<!-- 要使用此元件的父元件的模組 -->
<!-- parent.module.ts -->
<!--
import { UserCardOldModule } from './user-card/user-card.module';
// ...
@NgModule({
// ...
imports: [
// ...
UserCardOldModule
]
})
export class ParentModule {}
-->
(上述過去的寫法中,即使是一個簡單的元件,也需要 NgModule
來進行聲明和管理其依賴,並在使用時導入該模組。)
現在寫法 (Standalone Component):
元件可以直接管理自己的依賴,無需額外的 NgModule
。
<!-- user-card.component.ts (新版 - Standalone) -->
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common'; // 假設模板中用了 *ngIf 或其他 CommonModule 的功能
@Component({
selector: 'app-user-card-new',
standalone: true, // 關鍵!標記為 Standalone Component
imports: [CommonModule], // 直接在此處導入依賴 (其他 Standalone Components, Directives, Pipes 或 NgModules)
template: `
<div class="user-card">
@if (userName) {
<h4>歡迎回來,{{ userName }}!</h4>
<p>這是您的個人化卡片。</p>
} @else {
<p>請登入以查看您的卡片。</p>
}
</div>
`,
styles: [`.user-card { border: 1px solid #ccc; padding: 15px; border-radius: 5px; }`]
})
export class UserCardNewComponent {
@Input() userName: string = '';
}
<!-- 要使用此元件的父元件 (可以是 Standalone Component 或 NgModule-based Component) -->
<!-- parent.component.ts -->
<!--
import { UserCardNewComponent } from './user-card/user-card.component';
// ...
@Component({
standalone: true, // 假設父元件也是 Standalone
imports: [UserCardNewComponent], // 直接導入 Standalone Component
// ...
})
export class ParentComponent {}
-->
比較結果顯而易見:Standalone Components 大幅減少了定義和使用元件所需的樣板程式碼。元件的依賴關係在其自身的 imports
陣列中一目了然,不再需要透過 NgModule
進行中介管理,使得架構更扁平、更直觀。
深入瞭解 Standalone Components
什麼是 Standalone Components?
Standalone Components (以及 Directives 和 Pipes) 是指那些不屬於任何特定 NgModule
,並且能夠直接管理自身模板依賴的 Angular 建構塊。它們透過設定元數據中的 standalone: true
屬性來標識。
核心優勢
- 簡化架構與減少樣板:無需為每個元件、指令或管道都建立或尋找對應的
NgModule
。 - 更清晰的依賴管理:元件的依賴 (其他 Components, Directives, Pipes, NgModules) 直接在其
imports
陣列中聲明,一目了然。 - 提升學習曲線平緩度:對於 Angular 新手而言,可以先專注於 Component 的概念,而暫時無需深入理解複雜的
NgModule
體系。 - 易於共享與重用:Standalone Components 更容易在不同專案或函式庫之間共享。
- 潛在的建置優化:更細粒度的依賴關係有助於更精確的 Tree-shaking (搖樹優化)。
如何建立與使用 Standalone Component
- 標記為 Standalone: 在 Component (或 Directive, Pipe) 的
@Component
(或@Directive
,@Pipe
) 裝飾器中,設定standalone: true
。import { Component } from '@angular/core'; @Component({ standalone: true, selector: 'app-my-standalone-widget', template: `<p>我是一個獨立的小工具!</p>` }) export class MyStandaloneWidgetComponent {}
- 管理模板依賴: 使用
imports
陣列來導入該 Component 模板中需要用到的其他 Standalone Components, Directives, Pipes,或者傳統的 NgModules。import { Component } from '@angular/core'; import { CommonModule } from '@angular/common'; // 用於 *ngIf, *ngFor 等 import { AnotherStandaloneComponent } from '../another-standalone/another-standalone.component'; import { MyStandalonePipe } from '../my-standalone.pipe'; // import { SomeNgModule } from '../some-ng.module'; // 也可以導入 NgModule @Component({ standalone: true, selector: 'app-complex-standalone', imports: [ CommonModule, // 注意:v17+ 的新控制流 @if, @for 已無需 CommonModule AnotherStandaloneComponent, MyStandalonePipe, // SomeNgModule ], template: ` <h3>複雜獨立元件</h3> @if (isVisible) { <app-another-standalone></app-another-standalone> <p>{{ someValue | myStandalonePipe }}</p> } ` }) export class ComplexStandaloneComponent { isVisible = true; someValue = '測試文字'; }
- 使用 CLI 產生: Angular CLI 支援直接產生 Standalone Components:
自 Angular v17 起,ng generate component my-feature --standalone ng g c my-other-feature --standalone=true
ng new
建立的新專案預設就會使用 Standalone Components。
Standalone Components 中的依賴注入
依賴注入的機制在 Standalone Components 中基本保持不變。您仍然可以在建構函式中注入服務。主要的區別在於提供服務 (Providers) 的方式:
- 對於整個應用程式範圍的服務,推薦使用
providedIn: 'root'
。 - Standalone Components 可以有自己的 Providers,在其
@Component
裝飾器的providers
陣列中定義,這些 Providers 的作用域將限定在此 Component 及其子代。 - 路由級別的 Providers 也可以在路由設定中提供。
路由 (Routing) 與 Standalone Components
Standalone Components 可以直接用於路由,包括延遲載入:
// app.routes.ts
import { Routes } from '@angular/router';
// 假設 UserProfileComponent 和 ProductDetailComponent 都是 Standalone Components
export const routes: Routes = [
{
path: 'profile/:id',
loadComponent: () => import('./user-profile/user-profile.component').then(m => m.UserProfileComponent)
},
{
path: 'product/:productId',
component: ProductDetailComponent // 直接路由到一個 Standalone Component
},
// ... 其他路由
];
// main.ts 或 app.config.ts (引導應用程式)
// bootstrapApplication(AppComponent, {
// providers: [
// provideRouter(routes)
// ]
// });
使用 loadComponent
可以延遲載入一個 Standalone Component 及其相關的 JavaScript chunk。
與 NgModule 的關係與互通性
Standalone Components 並非要完全取代 NgModule,它們可以與現有的 NgModule 和諧共存,並逐步遷移。
- Standalone Component 導入 NgModule:一個 Standalone Component 可以在其
imports
陣列中導入一個 NgModule,以使用該 NgModule 所導出的 Components, Directives, Pipes。 - NgModule 導入 Standalone Component:一個 NgModule 也可以在其
imports
陣列中導入一個 Standalone Component,然後在其 `declarations` 中聲明的 Components 的模板裡使用這個 Standalone Component。
測試 Standalone Components
測試 Standalone Components 通常比測試 NgModule-based Components 更簡單,因為您只需要在測試環境中導入該 Standalone Component 及其直接的模板依賴,而無需處理複雜的 TestBed.configureTestingModule
中的 NgModule 設定。
// my-standalone.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyStandaloneComponent } from './my-standalone.component';
// 假設 MyStandaloneComponent 依賴了 CommonModule 和 AnotherStandaloneComponent
import { CommonModule } from '@angular/common';
import { AnotherStandaloneComponent } from '../another-standalone/another-standalone.component';
describe('MyStandaloneComponent', () => {
let component: MyStandaloneComponent;
let fixture: ComponentFixture<MyStandaloneComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
MyStandaloneComponent, // 直接導入待測試的 Standalone Component
// CommonModule, // 如果模板中用到 *ngIf 等,且 MyStandaloneComponent 內部沒有 import
// AnotherStandaloneComponent // 如果模板中用到
]
})
.compileComponents();
fixture = TestBed.createComponent(MyStandaloneComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
實際上,如果 MyStandaloneComponent
已經在其 imports
陣列中正確聲明了 CommonModule
和 AnotherStandaloneComponent
,那麼在 TestBed.configureTestingModule
的 imports
中可能只需要列出 MyStandaloneComponent
本身。
遷移至 Standalone Components
Angular CLI 提供了 schematics 來幫助將現有的 NgModule-based 應用遷移到使用 Standalone APIs:
ng generate @angular/core:standalone
這個命令會引導您完成遷移過程,包括將 declarations 轉換為 standalone,更新引導方式,以及將 NgModule 轉換為只提供路由設定等。建議在遷移大型專案時分階段進行並充分測試。
結論:Standalone Components —— 更現代、更簡潔的 Angular
Standalone Components 是 Angular 發展的一個重要方向,它大幅簡化了元件的定義和依賴管理,降低了學習門檻,並為建構更輕量、更模組化的應用程式提供了堅實的基礎。對於新專案,強烈建議直接採用 Standalone Components;對於現有專案,也可以考慮逐步遷移,以享受其帶來的諸多益處。
掌握 Standalone Components,將使您的 Angular 開發之旅更加高效和愉快!