Now that Angular 15 has made the Standalone APIs officially stable, many teams are considering migrating existing projects. Angular provides automated migration tooling, but understanding each step and its pitfalls is still essential. This article maps out the complete migration path.
Automated Migration Tools
# Angular 15 提供三个迁移 schematic,建议按顺序执行
# 第一步:将组件/指令/管道标记为 standalone
ng generate @angular/core:standalone
# 选择 "Convert all components, directives and pipes to standalone"
# 这会给每个组件加上 standalone: true,并将 NgModule 的 imports 移到组件的 imports
# 第二步:移除不必要的 NgModule
ng generate @angular/core:standalone
# 选择 "Remove unnecessary NgModule classes"
# 只保留 entryComponent 和 Feature 路由模块(如果还在用)
# 第三步:迁移 bootstrap
ng generate @angular/core:standalone
# 选择 "Bootstrap the project using standalone APIs"
# 将 platformBrowserDynamic().bootstrapModule(AppModule) 改为 bootstrapApplication
Key Points for Manual Migration
1. Move Component Dependencies from Module to Component
// 迁移前:组件通过 NgModule 获得依赖
@NgModule({
declarations: [UserCardComponent],
imports: [CommonModule, MatCardModule, RouterModule],
exports: [UserCardComponent],
})
export class UserCardModule {}
// 迁移后:组件直接声明依赖
@Component({
selector: "app-user-card",
standalone: true,
imports: [
NgIf,
NgFor, // 从 CommonModule 单独引入
MatCardModule,
RouterLink, // 从 RouterModule 单独引入
],
template: `...`,
})
export class UserCardComponent {}
2. Migrate Routing Modules
// 迁移前:Feature 路由模块
@NgModule({
imports: [
RouterModule.forChild([
{ path: '', component: UserListComponent },
{ path: ':id', component: UserDetailComponent }
])
]
})
export class UserRoutingModule {}
// 迁移后:路由数组(无 NgModule)
// users/user.routes.ts
export const USER_ROUTES: Routes = [
{ path: '', component: UserListComponent },
{ path: ':id', component: UserDetailComponent }
];
// 主路由中引用
{
path: 'users',
loadChildren: () => import('./users/user.routes').then(m => m.USER_ROUTES)
}
3. Migrate Global Providers
// 迁移前:AppModule
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
RouterModule.forRoot(routes),
StoreModule.forRoot(reducers), // NgRx
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
],
bootstrap: [AppComponent],
})
export class AppModule {}
// 迁移后:main.ts
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes, withPreloading(PreloadAllModules)),
provideHttpClient(
withInterceptors([authInterceptor]), // 函数式拦截器
),
provideStore(reducers), // NgRx standalone API
provideEffects([UserEffects]),
provideStoreDevtools(),
],
});
4. SharedModule 拆分
The trickiest part of the migration is usually the SharedModule (full of shared components):
// 迁移前:大型 SharedModule
@NgModule({
imports: [CommonModule, RouterModule, ...],
declarations: [ButtonComponent, CardComponent, TableComponent, ...],
exports: [ButtonComponent, CardComponent, TableComponent, CommonModule, RouterModule, ...]
})
export class SharedModule {}
// 迁移后:每个组件自包含,可以单独按需导入
// 不再需要 SharedModule,直接在需要的地方导入具体组件:
@Component({
standalone: true,
imports: [ButtonComponent, CardComponent], // 按需导入
...
})
export class SomePageComponent {}
Migration FAQ
// 问题 1:APP_INITIALIZER 怎么迁移
// 迁移前(NgModule)
providers: [{
provide: APP_INITIALIZER,
useFactory: (config: ConfigService) => () => config.load(),
deps: [ConfigService],
multi: true
}]
// 迁移后(bootstrapApplication)
providers: [{
provide: APP_INITIALIZER,
useFactory: () => {
const config = inject(ConfigService); // 使用 inject()
return () => config.load();
},
multi: true
}]
// 问题 2:懒加载的 standalone 组件 vs 懒加载路由组
// 单个 standalone 组件懒加载
{ path: 'about', loadComponent: () => import('./about.component').then(m => m.AboutComponent) }
// 多个组件组成的功能懒加载(推荐)
{ path: 'products', loadChildren: () => import('./product.routes').then(m => m.PRODUCT_ROUTES) }
Migration Progress Strategy
You don't have to migrate everything at once — incremental is the way:
阶段 1:新功能全部用 Standalone 开发
阶段 2:逐步迁移叶子组件(无子组件依赖的组件)
阶段 3:迁移 SharedModule 中的组件
阶段 4:迁移 Feature Module
阶段 5:迁移 AppModule,改用 bootstrapApplication
Summary
Angular 15's Standalone migration tooling lowers the cost of migrating, but large projects still need careful planning. The key thing to understand: Standalone isn't a 'new feature' — it's a replacement for NgModule. The two can coexist, and migration can proceed incrementally. After migrating, code volume typically drops 20-30% and the architecture becomes more intuitive.
