哈囉,各位邦友們!
昨天我們借助 resource()
API 整頓了 Heroes 專案的非同步狀態,感受 Signals 與 RxJS 攜手合作的威力。
今天換個角度,回到「專案骨架」本身,聊聊 Angular 從 NgModule
演進到 Standalone 的歷史,也複習我們專案一開始便採用的 Standalone 架構與實作細節。
NgModule
時代的設計思維與常見痛點。resource()
重構,對 bootstrapApplication
組態與 AppConfig
提供者不陌生。provideRouter
實作。NgModule
時代:集中註冊帶來的限制過去 Angular 依賴 NgModule
進行組態:所有元件、指令、管線必須先被宣告 (declarations),再由 imports
共享給其他模組。典型啟動流程如下:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { HeroesModule } from './heroes/heroes.module';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HeroesModule],
bootstrap: [AppComponent],
})
export class AppModule {}
這套模式在大型應用很受歡迎,但也帶來幾個實務痛點:
declarations
或 exports
而卡關。為了降低學習門檻,Angular 在 v14~v15 正式釋出 Standalone 元件,讓每個元件可以自帶 imports
,應用入口也不再需要 NgModule
。
Hero Journey 專案自一開始就採用 CLI 的 Standalone 模板,main.ts
透過 bootstrapApplication()
啟動應用:
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { App } from './app/app';
bootstrapApplication(App, appConfig).catch((err) => console.error(err));
AppConfig
則集中提供路由、HTTP、SSR 與 zoneless 設定:
import { ApplicationConfig, importProvidersFrom, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { routes } from './app.routes';
import { InMemoryData } from './in-memory-data';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes, withComponentInputBinding()),
provideBrowserGlobalErrorListeners(),
provideZonelessChangeDetection(),
provideHttpClient(withFetch()),
importProvidersFrom(HttpClientInMemoryWebApiModule.forRoot(InMemoryData, {
dataEncapsulation: false,
delay: 300,
post204: false,
put204: false,
})),
provideClientHydration(withEventReplay()),
],
};
在 Standalone 世界裡:
imports
使用其他 Standalone 元件或路由、表單等模組,一次完成紀錄與分享。provideRouter
、provideHttpClient
等函式化 API,讓 Day10、Day12 的設定改為純函式呼叫,更好拆測試與重構。因為我們從一開始就已經從 Standalone 架構出發,今天就當成一次複習,確認專案的每個重要環節。
main.ts
透過 bootstrapApplication()
直接引導根元件,省去 NgModule
。根元件 App
宣告 standalone: true
並明確列出依賴:@Component({
selector: 'app-root',
standalone: true,
imports: [RouterLink, RouterLinkActive, RouterOutlet],
templateUrl: './app.html',
styleUrl: './app.scss',
})
export class App {
protected readonly title = signal('hero-journey');
}
app.config.ts
以 ApplicationConfig
包裝路由、HTTP、SSR 與 zoneless 設定,透過 provideRouter()
、provideHttpClient()
、importProvidersFrom()
等函式化 API 統一調整。我們在 Day12~Day23 完成的服務、表單與資源重構,皆使用這套提供者模型。standalone: true
與專屬 imports
,需要時直接在使用端引入,降低測試與 Storybook 建置的心智負擔。ApplicationConfig
,TestBed
或 Storybook 的元件測試只需要匯入同一份設定即可重建環境,與傳統 NgModule
相比更容易維護。保持這些原則,就能確保專案持續受惠於 Standalone 架構的模組化與清晰依賴關係。
main.ts
改以 bootstrapApplication()
啟動,AppModule
不再參與流程。imports
引入依賴,不再依附於 declarations
。ApplicationConfig
,測試環境能以相同函式組態重建。Angular 從 NgModule
走到 Standalone,最大的價值在把焦點從模組搬回元件與功能本身。
下一篇我們會延伸到 @defer
,繼續探索現代 Angular 的效能優化能力。
bootstrapApplication
: