[翻译]Angular通过 3 步自动迁移到独立组件

511 阅读5分钟

虽然新的独立组件可以与传统的基于NgModule的Angular代码完美结合,但我们可能希望将项目完全迁移到新的独立空间。从 Angular 15.2 开始,有一个原理图(schematic)可以自动完成迁移任务。它通过 3 个步骤转换项目。在每个步骤之后,我们可以检查当前的进度并手动处理自动化无法处理的细节。

在这个简短的教程中,我将与您一起逐步完成这 3 个步骤并迁移我们的演示应用程序。

如果你想完成这些步骤,你可以在这里找到我们教程中基于 NgModule 的初始情况:

📂 github.com/manfredstey…
ngmodules 分支)

初步了解要迁移的应用程序

在检查了上述项目的分支之后,最好稍微浏览一下源代码。您应该识别以下 NgModules:ngmodules

  • 应用模块
  • 共享模块
  • 航班预订模块

另外,启动应用程序以获得它的第一印象:

ng serve -o

image.png

第 1 步

现在,让我们运行 Angular 15.2 提供的迁移原理图:

ng g @angular/core:standalone

当命令行显示选择迁移类型时,我们选择第一个选项(因为“从头开始”是一个很好的传统......)

image.png

当命令行显示迁移的路径时,我们可以按 Enter 键使用默认值:

image.png

此默认值指向项目的根目录。因此,整个项目将立即迁移。对于中小型应用程序,这应该没问题。但是,对于较大的应用程序,逐步迁移项目可能会很有趣。./

完成第一步后,您应该查看源代码并检查是否一切正常。对于此示例项目,您无需费心。原理图在这里做得很好!

第 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设置自定义记录器库。
  • 使用provideStoreprovideEffectsprovideStoreDevtools设置 NGRX 存储。
  • 将传统的拦截器替换为现在传递给HttpInterceptor withInterceptors的功能拦截器。为了使此步骤更容易,功能对应项从一开始就已经成为代码库的一部分。

有关自定义 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