I tried the Angular Standalone migration, and here is the result
Last week Minko Gechev tweeted about an Angular schematic to automate the migration from @NgModules
to the standalone API. Of course, I had to try out this migration myself.
https://twitter.com/mgechev/status/1621307204002512897
To test the migration I created a small Angular application, which I will use as a starting point for the migration. While the application is small, it contains a little bit of everything, child components, eager and lazy loaded modules, a pipe, a directive, a couple of tests, and a service.
The schematic is available starting from Angular version 15.2.0-next.2. To update to this version or a later version, run the following command:
You can take a look at the before branch on GitHub if you're interested in the code from the example project.
To run the migration open a new CLI terminal at the root of the Angular project and run the @angular/core:standalone
schematic:
This gives you three options:
- Convert all components, directives, and pipes to standalone
- Remove unnecessary NgModule classes
- Bootstrap the application using standalone APIs
To complete the migration, you need to run all three options. Instead of migrating your whole codebase at once, you can also run the schematic on specific directories.
The first time I ran the schematic I tried to keep the application running and the tests green. For this, I had to manually update some parts of the code and tests (see the steps below). But while running the next options, I noticed that the schematic was also fixing some of the issues I had to fix manually. That's why I decided to run the schematic from the start again, but this time I ran the schematics after each other without updating the code and tests. Looking back at it, I think the latter is the way to go, although it seems not to be recommended in the Angular docs.
The schematic only migrates the code from NgModule
s to the new standalone API syntax.
But, lately Angular also added a bunch of new functional APIs.
For the completeness of this migration, I also manually migrated some features that are not covered by the schematic to their new equivalent functional API version.
- Migrate to provideRouter
- Migrate to provideHttpClient
- Migrate to functional router guards
- Update tests, only import standalone components
If you're not interested in the details, you can take a look at the migrated version on the main branch (with manual changes between migration steps) or on the after branch (all migrations at once, and manual changes afterward).
Why you should migrate link
I think you should migrate to the standalone components because it has a few benefits.
The foremost is that Angular has a smoother learning curve for new developers. For new and experienced developers, a big advantage is a simplified codebase, which is easier to understand and maintain.
It also has a few performance benefits, e.g. you can lazy load a component because it defines its own dependencies explicitly.
Another benefit is that your tests require less setup code. In most cases, you only need to import the component you want to test and mock the external dependencies e.g. an HTTP service.
And who knows, perhaps somewhere in the future that Angular can automagically import the dependencies for you, and is step this step just an intermediate step to make that possible. But for now, you need to do it manually.
From the docs docs:
format_quoteStandalone 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
NgModule
s. Existing applications can optionally and incrementally adopt the new standalone style without any breaking changes.
1. Convert all components, directives, and pipes to standalone link
Commit: d32df876bebc4f1824589bca14799cc27d6ff602:
Command link
Results link
- Components, directives, and pipes are migrated to the standalone version
- Dependencies are added to the standalone versions
NgModule
s are updated, e.g. components are moved from thedeclarations
to theimports
Manual changes link
- A child component referenced in a Route was not migrated. This was fixed in the next migration while running all schematics at once.
- Update TestBed: move standalone components/directives/pipes from
declarations
toimports
- Declarables are moved from the
declarations
to theimports
of anNgModule
Notes link
AppComponent
is not migrated- It also imports an internal
ɵInternalFormsSharedModule
module together with theFormsModule
orReactiveFormsModule
Migration Examples link
Components are migrated to standalone components:
standalone
is set totrue
- dependencies are added to
imports
NgModule
s are updated by moving the declarations
to the imports
:
2. Remove unnecessary NgModule classes link
Commit: c74471ae5b9627ab73ed0e163600834d4d51f85d
Command link
Results link
- Files only containing an
NgModule
are deleted NgModules
s that reference the removedNgModule
s are updated
Manual changes link
- Update TestBed: remove deleted
NgModule
s - Commented a child component in
AppComponent
. This was fixed in the next migration while running all schematics at once.
Migration Examples link
The file shared.module.ts
is deleted because it only contained an NgModule
, SharedModule
:
NgModules
s that reference the removed NgModules
are updated.
3. Bootstrap the application using standalone APIs link
Commit: 16c649d64130741ea75e4d35517ffd6b5b80cdc8
Command link
Result link
main.ts
is updated fromplatformBrowserDynamic().bootstrapModule(AppModule)
tobootstrapApplication(AppComponent)
Manual changes link
- Readded the child component that was removed in the previous step. This was not needed while running all the schematics at once.
Notes link
AppModule
still exists, but the content is commented out- It seems like files are imported by using the
\\
separator instead of/
Migration Examples link
4. Remove unnecessary NgModule classes (for AppModule) link
Commit: c05ca76aad7717e303037e33c269602627ab9720
Command link
Result link
- Now that
AppModule
is not used anymore, it is deleted
5. Migrate to provideRouter link
Commit: 528661c9cef1e9f3bf5cb83ff6571c96c4ae8164
This is not an automatic migration.
Result link
We can use provideRouter()
instead of RouterModule.forRoot()
and RouterModule.forChild()
.
For more info about provideRouter
see Angular Router Standalone APIs by Kevin Kreuzer.
Migration Examples link
6. Migrate to provideHttpClient link
Commit: 655217f3f528fc7db83515cfce59275043dd6183
This is not an automatic migration.
Result link
Instead of importing HttpClientModule
in AppModule
, and registering interceptors as providers with HTTP_INTERCEPTORS
we can now use provideHttpClient()
.
For more info about provideHttpClient
see The Refurbished HttpClient in Angular 15 – Standalone APIs and Functional Interceptors by Manfred Steyer.
Migration Examples link
7. Migrate to functional router guards link
Commit: 6b1977d24e9770871f432b0eaa0e24efd94d41fe
This is not an automatic migration.
Result link
A router guard that was implemented as a class can be refactored to a function.
For more info about functional router guards see How To Use Functional Router Guards in Angular by Dany Paredes .
It's probably best to immediately migrate to the new canMatch
guard, for more info see Introducing the CanMatch Router Guard In Angular by Netanel Basal.
Migration Examples link
Before:
After:
8. Update tests, only import standalone components link
Commit: 7e04027511b8ece03522bb3e52e87775e4f7dd8a
This is not an automatic migration.
Result link
Because a component now contains all its dependencies, we can refactor the test cases. The test becomes simpler because we are not required to import all the dependencies anymore. Instead, we can import the component itself.
Migration Examples link
Feel free to update this blog post on GitHub, thanks in advance!
Join My Newsletter (WIP)
Join my weekly newsletter to receive my latest blog posts and bits, directly in your inbox.
Support me
I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.