Migrations

Migrate an existing Angular project to standalone

Standalone components provide a simplified way to build Angular applications. Standalone components, directives, and pipes aim to streamline the authoring experience by reducing the need for NgModules. Existing applications can optionally and incrementally adopt the new standalone style without any breaking changes.

This schematic helps to transform components, directive and pipes in existing projects to become standalone. The schematic aims to transform as much code as possible automatically, but it may require some manual fixes by the project author.

Run the schematic using the following command:

      
ng generate @angular/core:standalone

Before updating

Before using the schematic, please ensure that the project:

  1. Is using Angular 15.2.0 or later.
  2. Builds without any compilation errors.
  3. Is on a clean Git branch and all work is saved.

Schematic options

Option Details
mode The transformation to perform. See Migration modes below for details on the available options.
path The path to migrate, relative to the project root. You can use this option to migrate sections of your project incrementally.

Migrations steps

The migration process is composed of three steps. You'll have to run it multiple times and check manually that the project builds and behaves as expected.

Note: While the schematic can automatically update most code, some edge cases require developer intervention. You should plan to apply manual fixes after each step of the migration. Additionally, the new code generated by the schematic may not match your code's formatting rules.

Run the migration in the order listed below, verifying that your code builds and runs between each step:

  1. Run ng g @angular/core:standalone and select "Convert all components, directives and pipes to standalone"
  2. Run ng g @angular/core:standalone and select "Remove unnecessary NgModule classes"
  3. Run ng g @angular/core:standalone and select "Bootstrap the project using standalone APIs"
  4. Run any linting and formatting checks, fix any failures, and commit the result

After the migration

Congratulations, your application has been converted to standalone 🎉. These are some optional follow-up steps you may want to take now:

  • Find and remove any remaining NgModule declarations: since the "Remove unnecessary NgModules" step cannot remove all modules automatically, you may have to remove the remaining declarations manually.
  • Run the project's unit tests and fix any failures.
  • Run any code formatters, if the project uses automatic formatting.
  • Run any linters in your project and fix new warnings. Some linters support a --fix flag that may resolve some of your warnings automatically.

Migration modes

The migration has the following modes:

  1. Convert declarations to standalone.
  2. Remove unnecessary NgModules.
  3. Switch to standalone bootstrapping API. You should run these migrations in the order given.

Convert declarations to standalone

In this mode, the migration converts all components, directives and pipes to standalone by removing standalone: false and adding dependencies to their imports array.

HELPFUL: The schematic ignores NgModules which bootstrap a component during this step because they are likely root modules used by bootstrapModule rather than the standalone-compatible bootstrapApplication. The schematic converts these declarations automatically as a part of the "Switch to standalone bootstrapping API" step.

Before:

      
// shared.module.ts@NgModule({  imports: [CommonModule],  declarations: [GreeterComponent],  exports: [GreeterComponent]})export class SharedModule {}
      
// greeter.component.ts@Component({  selector: 'greeter',  template: '<div *ngIf="showGreeting">Hello</div>',  standalone: false,})export class GreeterComponent {  showGreeting = true;}

After:

      
// shared.module.ts@NgModule({  imports: [CommonModule, GreeterComponent],  exports: [GreeterComponent]})export class SharedModule {}
      
// greeter.component.ts@Component({  selector: 'greeter',  template: '<div *ngIf="showGreeting">Hello</div>',  imports: [NgIf]})export class GreeterComponent {  showGreeting = true;}

Remove unnecessary NgModules

After converting all declarations to standalone, many NgModules can be safely removed. This step deletes such module declarations and as many corresponding references as possible. If the migration cannot delete a reference automatically, it leaves the following TODO comment so that you can delete the NgModule manually:

      
/* TODO(standalone-migration): clean up removed NgModule reference manually */

The migration considers a module safe to remove if that module:

  • Has no declarations.
  • Has no providers.
  • Has no bootstrap components.
  • Has no imports that reference a ModuleWithProviders symbol or a module that can't be removed.
  • Has no class members. Empty constructors are ignored.

Before:

      
// importer.module.ts@NgModule({  imports: [FooComponent, BarPipe],  exports: [FooComponent, BarPipe]})export class ImporterModule {}

After:

      
// importer.module.ts// Does not exist!

Switch to standalone bootstrapping API

This step converts any usages of bootstrapModule to the new, standalone-based bootstrapApplication. It also removes standalone: false from the root component and deletes the root NgModule. If the root module has any providers or imports, the migration attempts to copy as much of this configuration as possible into the new bootstrap call.

Before:

      
// ./app/app.module.tsimport { NgModule } from '@angular/core';import { AppComponent } from './app.component';@NgModule({  declarations: [AppComponent],  bootstrap: [AppComponent]})export class AppModule {}
      
// ./app/app.component.ts@Component({  selector: 'app',  template: 'hello',  standalone: false,})export class AppComponent {}
      
// ./main.tsimport { platformBrowser } from '@angular/platform-browser';import { AppModule } from './app/app.module';platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));

After:

      
// ./app/app.module.ts// Does not exist!
      
// ./app/app.component.ts@Component({  selector: 'app',  template: 'hello'})export class AppComponent {}
      
// ./main.tsimport { bootstrapApplication } from '@angular/platform-browser';import { AppComponent } from './app/app.component';bootstrapApplication(AppComponent).catch(e => console.error(e));

Common problems

Some common problems that may prevent the schematic from working correctly include:

  • Compilation errors - if the project has compilation errors, Angular cannot analyze and migrate it correctly.
  • Files not included in a tsconfig - the schematic determines which files to migrate by analyzing your project's tsconfig.json files. The schematic excludes any files not captured by a tsconfig.
  • Code that cannot be statically analyzed - the schematic uses static analysis to understand your code and determine where to make changes. The migration may skip any classes with metadata that cannot be statically analyzed at build time.

Limitations

Due to the size and complexity of the migration, there are some cases that the schematic cannot handle:

  • Because unit tests are not ahead-of-time (AoT) compiled, imports added to components in unit tests might not be entirely correct.
  • The schematic relies on direct calls to Angular APIs. The schematic cannot recognize custom wrappers around Angular APIs. For example, if there you define a custom customConfigureTestModule function that wraps TestBed.configureTestingModule, components it declares may not be recognized.