在 Angular 14 新增了 Standalone Component
的功能,以往我們新增的 Component、Directive 以及 Pipe 可以不需要透過 NgModule
來管理元件,同時也能簡化使用 Angular 開發應用的體驗。現有應用可以選擇性逐步採用新的獨立樣式,而無需進行任何重大更改。
如何使用 Standalone Component
建立 Standalone Component
語法相當簡單,只需要在 ng generate
指令後面加上 --standalone
即可:
ng generate component <component-name> --standalone
Component 轉換成 Standalone Component
如果要在既有的 Component 轉換成 Standalone Component,只需要在 @Component
裡面加上 standalone: true
:
@Component({
standalone: true,
selector: 'photo-gallery',
imports: [ImageGridComponent],
template: ` ... <image-grid [images]="imageList"></image-grid> `,
})
export class PhotoGalleryComponent {
// component logic
}
加上 standalone: true
之後,我們就可以在 Component 使用 imports 來引入其他的 Dependency,像是 Directive、Pipe、Component 等等。此外 imports
也可以引入其他的 NgModule。
既有的 NgModule 加入 Standalone Component
Standalone Component 透過 NgModule.imports
就可以加到既有的 NgModule 中:
@NgModule({
declarations: [AlbumComponent],
exports: [AlbumComponent],
imports: [PhotoGalleryComponent],
})
export class AlbumModule {}
直接從 Standalone Component 執行應用
我們可以不用透過任何 NgModule
來啟動應用,Angular 提供了 bootstrapApplication
API,可以直接從 Standalone Component 啟動:
// in the main.ts file
import { bootstrapApplication } from '@angular/platform-browser';
import { PhotoAppComponent } from './app/photo.app.component';
bootstrapApplication(PhotoAppComponent);
新的 Routing API 簡化 lazy-loading
Angular router API 也更新並簡化,以便利用 Standalone Component,在許多常見 lazy-loading 的情境不再需要 NgModule
。例如要 lazy-loading 一個 Component,只需要在 Route.loadComponent
裡面使用 import()
來引入 Component 即可:
export const ROUTES: Route[] = [
{
path: 'admin',
loadComponent: () =>
import('./admin/panel.component').then((mod) => mod.AdminPanelComponent),
},
// ...
];
一次 lazy-loading 多個路由
LoadChildren
現在支援加載一組新的子路由,不需要寫一個 lazy-load 的 NgModule
,利用 RouterModule.forChild
宣告路由:
// In the main application:
export const ROUTES: Route[] = [
{
path: 'admin',
loadChildren: () =>
import('./admin/routes').then((mod) => mod.ADMIN_ROUTES),
},
// ...
];
// In admin/routes.ts:
export const ADMIN_ROUTES: Route[] = [
{ path: 'home', component: AdminHomeComponent },
{ path: 'users', component: AdminUsersComponent },
// ...
];
Lazyloading 與 default exports
上面的範例中 ADMIN_ROUTES
也可以改成 export default
,如此一來在 loadChildren
或 loadComponent
都只需要 import('./admin/routes')
:
// In the main application:
export const ROUTES: Route[] = [
{ path: 'admin', loadChildren: () => import('./admin/routes') },
// ...
];
// In admin/routes.ts:
export default [
{ path: 'home', component: AdminHomeComponent },
{ path: 'users', component: AdminUsersComponent },
// ...
] as Route[];
為部分路由提供服務的方法
對於 NgModules 的 lazy-load API(即 loadChildren
),在讀取路由的延遲載入子路由時,會創建一個新的模組注入器(Injector)。這個功能經常被用來為特定的路由提供 service。舉個例子,如果把所有 /admin
下的路由都用 loadChildren
來設定範圍,那麼只有這些路由才能獲得針對 Admin 的特定 service。要做到這一點,即使不需要延遲載入相關路由,也需要使用 loadChildren
API。
如今,Router 允許在路由上明確指定額外的 Providers
,這樣可以在不需要延遲載入或 NgModule
的情況下實現相同的範圍設定。舉例來說,/admin
路由結構內範圍限定的 service 將如下:
export const ROUTES: Route[] = [
{
path: 'admin',
providers: [AdminService, { provide: ADMIN_API_KEY, useValue: '12345' }],
children: [
{ path: 'users', component: AdminUsersComponent },
{ path: 'teams', component: AdminTeamsComponent },
],
},
// ... other application routes that don't
// have access to ADMIN_API_KEY or AdminService.
];
我們可以將 provider 與額外路由配置的 loadChildren
相結合,以實現延遲載入帶有額外路由和路由級 provider 的 NgModule 的相同效果。這個例子配置了與上述相同的 provider/子路由,但是在延遲載入的邊界之後:
// Main application:
export const ROUTES: Route[] = {
// Lazy-load the admin routes.
{path: 'admin', loadChildren: () => import('./admin/routes').then(mod => mod.ADMIN_ROUTES)},
// ... rest of the routes
}
// In admin/routes.ts:
export const ADMIN_ROUTES: Route[] = [{
path: '',
pathMatch: 'prefix',
providers: [
AdminService,
{provide: ADMIN_API_KEY, useValue: 12345},
],
children: [
{path: 'users', component: AdminUsersCmp},
{path: 'teams', component: AdminTeamsCmp},
],
}];
要留意一下空路徑的路由下的 providers 是在所有的子路由共享的。 另外,importProvidersFrom
這個方法可以 import 基於 NgModule
的 DI 注入到 Route 的 providers 中:
export const ROUTES: Route[] = [
{
path: 'foo',
providers: [importProvidersFrom(NgModuleOne, NgModuleTwo)],
component: YourStandaloneComponent,
},
];
小結
當專案規模越來越大的時候 NgModule 的管理可能會面臨挑戰,時常要思考是否要建立新的 NgModule,或是這個元件是否會被重覆使用,一不小心就進入了重構地獄。Standalone Coponent 算是把 NgModule 一部分的功能下放到元件的層級,這樣可以避免建立元件時可能的摩擦,並且簡化了學習歷程,同時也可以讓延遲載入變得更容易。未來需要什麼東西就直接 import 到元件中,不需要再去管 NgModule 的事情。
參考資料
Angular Standalone Component
Getting started with Angular Standalone Component