Search code examples
angularangular17

Best practice for main.ts bootstrap call in Angular 17+ standalone configuration


I have a question about the bootstrap call in main.ts in the Angular 17 standalone API configuration. In particular: should providers/imports be added to the bootstrap call in addition to being imported into the standalone components, directives and pipes?

Some context:

I migrated a legacy Angular app to the new standalone API configuration. I used the official standalone migration guide. This schematic contains four migration steps, the third of which is:

Run ng g @angular/core:standalone and select "Bootstrap the project using standalone APIs"

This step copies the providers/imports from the root - NgModule - module into the new bootstrap call in main.ts. Mine now looks like this:

bootstrapApplication(AppComponent, {
    providers: [
        importProvidersFrom(AppRoutingModule, BrowserModule, ReactiveFormsModule, 
        MatToolbarModule, MatButtonModule, MatIconModule, FormsModule, MatFormFieldModule, MatInputModule, MatProgressSpinnerModule),
        provideAnimations(),
        provideHttpClient(withInterceptorsFromDi())
    ]
}).catch(err => console.error(err));

You can see that, for example, the Angular Material imports have been added to the providers array in the bootstrap call. These have also imported individually in the components where they are needed, as is necessary in the standalone API setup.

So:

  • why are they also imported in the bootstrap call?

  • should I add any new imports I use in future to the bootstrap call as well? i.e.: if in future I decide to use an Autocomplete, should I add the MatAutocompleteModule to the bootstrap call as well as in the component?

Update

As per the comments/answers below, the migration schematic adds more to the bootstrapApplication method than is necessary/desirable (to ensure a smooth migration).

I recommend refactoring it to the app.config.ts pattern.

After refactoring my setup is:

main.ts

bootstrapApplication(AppComponent, appConfig)
    .catch((err) => console.error(err));

app.config.ts

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideAnimations(),
    provideHttpClient()
  ]
};

The other imports (see bootstrapApplication in the original post) were either obsolete or should only be imported at component level.

Some further information: Getting started with standalone components


Solution

  • To me your target should look like this.

    bootstrapApplication(AppComponent, {
        providers: [
            provideRouter(myRoutes)
            importProvidersFrom(ReactiveFormsModule),
            provideAnimations(),
            provideHttpClient(withInterceptorsFromDi())
        ]
    }).catch(err => console.error(err));
    
    • The BrowserModule is automaticially imported by the bootstrapApplication function.

    • Routing should be provided by passing the routes to provideRouter

    • Every Material import should be at a component level. Each standalone component should only import the material module it needs

    • As improvements, you should look into replacing provideAnimations with provideAnimationsAsync, to lazy load the animation package

    • FormsModule still requires to be loading, so it could be fine here.

    • You could replace withInterceptorsFromDi() with provideInterceptors if you convert your interceptors to InterceptorFn