虽然新的独立组件可以与传统的基于NgModule的Angular代码完美结合,但我们可能希望将项目完全迁移到新的独立空间。从 Angular 15.2 开始,有一个原理图(schematic)可以自动完成迁移任务。它通过 3 个步骤转换项目。在每个步骤之后,我们可以检查当前的进度并手动处理自动化无法处理的细节。
在这个简短的教程中,我将与您一起逐步完成这 3 个步骤并迁移我们的演示应用程序。
如果你想完成这些步骤,你可以在这里找到我们教程中基于 NgModule 的初始情况:
📂 github.com/manfredstey…
(ngmodules 分支)
初步了解要迁移的应用程序
在检查了上述项目的分支之后,最好稍微浏览一下源代码。您应该识别以下 NgModules:ngmodules
- 应用模块
- 共享模块
- 航班预订模块
另外,启动应用程序以获得它的第一印象:
ng serve -o
第 1 步
现在,让我们运行 Angular 15.2 提供的迁移原理图:
ng g @angular/core:standalone
当命令行显示选择迁移类型时,我们选择第一个选项(因为“从头开始”是一个很好的传统......)
当命令行显示迁移的路径时,我们可以按 Enter 键使用默认值:
此默认值指向项目的根目录。因此,整个项目将立即迁移。对于中小型应用程序,这应该没问题。但是,对于较大的应用程序,逐步迁移项目可能会很有趣。./
完成第一步后,您应该查看源代码并检查是否一切正常。对于此示例项目,您无需费心。原理图在这里做得很好!
第 2 步
现在,让我们再次运行原理图进行第二步:
输出显示已删除,其他模块已更新。还好,它仍然存在——它将在第 3 步中删除。但是,所有其他NgModule现在应该都消失了。不幸的是,仍然存在:SharedModule AppModule FlightBookingModule
// src/app/booking/flight-booking.module.ts
@NgModule({
imports: [
CommonModule,
FormsModule,
StoreModule.forFeature(bookingFeature),
EffectsModule.forFeature([BookingEffects]),
RouterModule.forChild(FLIGHT_BOOKING_ROUTES),
FlightCardComponent,
FlightSearchComponent,
FlightEditComponent,
PassengerSearchComponent
],
exports: [],
providers: []
})
export class FlightBookingModule { }
如此列表所示,不再做太多事情了。但是,该部分中有一些对方法的调用。这些方法用于设置路由器和 NGRX 存储。由于它们是特定于库的,因此自动化无法将它们转换为等效的独立 API 调用。所以,我们需要手工处理这个问题。FlightBookingModule imports
RouterModule.forChild设置一些与 一起加载的子路由。但是,在独立世界中,我们不再需要 NgModules 来设置子路由。相反,父路由配置可以直接指向子路由。因此,让我们切换到文件并更新触发延迟加载的路由:FlightBookingModule app.routes.ts
// src/app/app.routes.ts
{
path: 'flight-booking',
canActivate: [() => inject(AuthService).isAuthenticated()],
loadChildren: () =>
import('./booking/flight-booking.routes')
.then(m => m.FLIGHT_BOOKING_ROUTES)
},
请注意,现在直接导入flight-booking.routes。不再通过 进行间接处理。甚至有可能进一步缩短:如果文件导出路由作为其默认导出,我们可以跳过后续调用:import FlightBookingModule flight-booking.routes.ts then
{
path: 'flight-booking',
canActivate: [() => inject(AuthService).isAuthenticated()],
loadChildren: () =>
import('./booking/flight-booking.routes')
},
为了确保 NGRX 存储已为此惰性应用程序部分初始化,我们可以直接为惰性子路由注册相应的提供程序:
// src/app/booking/flight-booking.routes.ts
import { importProvidersFrom, inject } from '@angular/core';
[...]
export const FLIGHT_BOOKING_ROUTES: Routes = [
{
path: '',
component: FlightBookingComponent,
canActivate: [() => inject(AuthService).isAuthenticated()],
providers: [
importProvidersFrom(StoreModule.forFeature(bookingFeature)),
importProvidersFrom(EffectsModule.forFeature([BookingEffects])),
],
children: [
[...]
],
},
];
此新的提供程序阵列设置的服务仅手头路由及其子路由需要。该函数桥接到 NgModule,并允许检索他们的providers。importProvidersFrom
现在,我们可以删除 ()。FlightBookingModule src/app/booking/flight-booking.module.ts
第 3 步
让我们第三次运行迁移原理图:
这将删除并更新文件用来启动 .完成此步骤后,程序像以前一样运行:AppModule main.ts AppComponent
ng serve -o
迁移到独立 API
可以看到,它仍然引用了几个模块:main.ts importProvidersFrom
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(
BrowserModule,
LayoutModule,
LoggerModule.forRoot({
level: LogLevel.DEBUG,
appenders: [DefaultLogAppender],
formatter: (level, cat, msg) => [level, cat, msg].join(';'),
}),
StoreModule.forRoot(reducer),
EffectsModule.forRoot(),
StoreDevtoolsModule.instrument(),
MatToolbarModule,
MatButtonModule,
MatSidenavModule,
MatIconModule,
MatListModule
),
{
provide: HTTP_INTERCEPTORS,
useClass: LegacyInterceptor,
multi: true,
},
provideAnimations(),
provideHttpClient(withInterceptorsFromDi()),
provideRouter(APP_ROUTES, withPreloading(PreloadAllModules)),
],
});
此外,还有一个老的基于类的注册,并且通过调用HttpInterceptor HttpClient .通过移动到独立withInterceptorsFromDi API,这可以得到改进:
bootstrapApplication(AppComponent, {
providers: [
provideLogger({
level: LogLevel.DEBUG,
appenders: [DefaultLogAppender],
formatter: (level, cat, msg) => [level, cat, msg].join(';'),
}),
provideStore(reducer),
provideEffects(),
provideStoreDevtools(),
provideAnimations(),
provideHttpClient(withInterceptors([authInterceptor])),
provideRouter(APP_ROUTES, withPreloading(PreloadAllModules)),
importProvidersFrom(
LayoutModule,
MatToolbarModule,
MatButtonModule,
MatSidenavModule,
MatIconModule,
MatListModule
),
],
});
修改的内容涉及以下方面:
- 删除在引导独立组件时不需要显式导入的导入。
BrowserModule - 使用
provideLogger设置自定义记录器库。 - 使用
provideStore、provideEffects和provideStoreDevtools设置 NGRX 存储。 - 将传统的拦截器替换为现在传递给
HttpInterceptorwithInterceptors的功能拦截器。为了使此步骤更容易,功能对应项从一开始就已经成为代码库的一部分。
有关自定义 Standalone API (如 provideLogger)的更多信息,请参阅此处。
NGRX希望其独立API被完全使用或根本不使用。因此,我们还需要返回 并将调用替换为调用 和 :flight-booking.routes.ts importProvidersFrom provideState provideEffects
export const FLIGHT_BOOKING_ROUTES: Routes = [
{
path: '',
component: FlightBookingComponent,
canActivate: [() => inject(AuthService).isAuthenticated()],
providers: [
provideState(bookingFeature),
provideEffects(BookingEffects)
],
children: [
[...]
],
},
];
请注意,虽然我们在设置Store时会调用 provideStore, 但我们需要在应用程序的其他地方调用 (!) provideState 来为它们设置额外的功能切片。然而,可以在这两个地方都调用 provideEffects 来为根级别和功能切片设置效果。这是在使用 Angular 开发时使用 NGRX 状态管理库时的注意事项。main.ts
进行此修改后,应用程序将迁移到独立组件和 API。通过以下方式运行它
ng serve -o
结论
新的原理图可自动迁移到独立组件。通过三步整个应用程序或仅其中的一部分被转移到使用Angular的新轻量级方式。在每个步骤之后,我们可以检查执行的修改并进行干预。
本文翻译自 Automatic Migration to Standalone Components in 3 Steps - ANGULARarchitects