Angular Router Standalone APIs
Let's take a look at Angulars brand new standalone Router APIs. Is it worth it? How much can we shake off the Routers bundle?
Kevin Kreuzer
@kreuzercode
Oct 18, 2022
6 min read
A couple of days ago, I came across this Tweet from Minko Gechev.
Have you noticed the new standalone router APIs?
— Minko Gechev (@mgechev) October 4, 2022
They allow you to use the router without NgModules and are tree-shakable!
You can shave off 11% of the router bundle 🔥https://t.co/3zAVScezUv
Currently in developer preview, stable in v15!
Shave off 11%
; that’s neat! 😎 We have to try this!
I immediately grepped a fresh coffee and started to code a simple application with two lazy-loaded routes (/movies
& /shows
) that allows us to list movies and tv-shows.
🤫 Pro tip! You can create lazy-loaded feature modules with the use of a simple single command!
Movies | Shows |
---|---|
Sample application that lists Movies and TV shows. (rankings are based on my own opinions 😉).
Great! We have a nice App with two routes. Each route has a service that delivers the items and some components to
display them. Nothing special.
Let’s focus on the exciting part, which for this article is the bundle size.
Initial Bundle size report from the source map explorer
The whole app is 246.23 KB
, 203.17
KB of it is the main bundle and 65.62 KB
the router. That’s currently 26.6%
of our
current application. 26.6%
may sound like a lot, but that's because our app is very trivial and, therefore, very small.
Okay, so that's our baseline. Let’s refactor our application to use the router's standalone APIs.
Standalone APIs
Before using the standalone API, we should consult the official docs to
understand how to use the router standalone APIs fully.
Angular now provides a provideRouter
function. This function allows us to provide our application the necessary Router
functionality (routes and router features). Today, those router functionalities are provided by the RouterModule
.
Great, so we have a way to provide the router functionalities, but how about components and directives, such asrouter-outlet
or routerLink
. How do we get them? It’s pretty simple; we can import them. All of the router components
and directives are now available as standalone APIs.
Fantastic, it seems like the new APIs provide everything we need. How are we supposed to use the provideRouter
function?
We can pass the provideRouter
function as an options parameter to the bootstrapApplication
function.
const appRoutes: Routes = [];
bootstrapApplication(AppComponent, {
providers: [
provideRouter(
appRoutes,
withDebugTracing(),
withRouterConfig({ paramsInheritanceStrategy: 'always' }),
),
],
});
bootstrapApplication
? What is that? I haven’t used nor seen this function yet. In my app, we use the bootstrapModule
function provided by the platformBrowserDynamic
function.
The bootstrapApplication
function was introduced in version 14 and allows us to bootstrap a standalone component.
Hmm… does that mean I need to change my application to standalone components to take advantage of the provideRouter
function?
Yes, at least for the AppComponent
. Let’s test two different approaches.
In the first approach, we will convert our AppComoponent
to a standalone component, but we will still use the RouterModule
in our lazy loaded features. We will convert the whole app to standalone components in the second approach.
Hybrid approach
To take advantage of the provideRouter
function, we have to convert our AppComponent
to a standalone component by adding
a standalone property with the value of true
.
Since our template uses router-outlet
and routerLink
we additionally have to import the Standalone APIs RouterOutlet
andRouterLinkWithHref
.
import { Component } from '@angular/core';
import { RouterLinkWithHref, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
imports: [RouterOutlet, RouterLinkWithHref],
standalone: true,
})
export class AppComponent {}
Once we converted our AppComponent
to a standalone component, we can delete the app.module.ts
and theapp.routing.module.ts
. At this point, there's only one step left which is to adjust our main.ts
.
import { enableProdMode } from '@angular/core';
import { provideRouter, Routes } from '@angular/router';
import { bootstrapApplication } from '@angular/platform-browser';
import { environment } from './environments/environment';
import { AppComponent } from './app/app.component';
const routes: Routes = [
{
path: 'shows',
loadChildren: () =>
import('./app/features/shows/shows.module').then((m) => m.ShowsModule),
},
{
path: 'movies',
loadChildren: () =>
import('./app/features/movies/movies.module').then((m) => m.MoviesModule),
},
];
if (environment.production) {
enableProdMode();
}
bootstrapApplication(AppComponent, {
providers: provideRouter(routes),
});
We moved the routes from the app.routing.module.ts
into our main.ts
and adjusted the paths. Next, we pass it to theprovideRouter
function in the providers
options of the bootstrapApplication
function.
Great! We successfully used some of the routers standalone APIs. Let’s explore the bundle size of our application.
Bundle size of our application that now uses some of the routers standalone APIs as well as the RouterModule
.
The overall app size decreased to 243.08 KB
. The interesting part though, is that even the total size of the application
went down, the router itself increased by 0.13 KB
.
Well, that's because we now use the provideRouter
function in combination with the RouterModule
which we still use in
the lazy loaded feature modules.
So, this approach doesn’t bring us the desired bundle size reduction of the router. But it’s still a nice approach since
it can be used as a partial migration step towards full standalone components.
Full standalone components
Let’s push this over the goal line. Let’s convert our lazy-loaded routing modules into lazy loaded standalone
components.
To do so we will first remove the movies.routing.module.ts
and the movies.module.ts
files. Next, we have to convert themovies.component.ts
into a standalone component.
import { Component } from '@angular/core';
import { NgFor } from '@angular/common';
import { MoviesService } from './movies.service';
import { MovieCardComponent } from './movie-card/movie-card.component';
@Component({
selector: 'app-movies',
templateUrl: './movies.component.html',
styleUrls: ['./movies.component.scss'],
standalone: true,
imports: [MovieCardComponent, NgFor],
})
export class MoviesComponent {
movies = this.moviesService.getMovies();
constructor(private moviesService: MoviesService) {}
}
We set standalone in the components decorator to true and add an imports array that contains a value ofMovieCardComponent
and NgFor
.
Behind the scenes I already converted the
MovieCardComponent
to a standalone component as well.
Of course, the same steps must be executed for the shows route. But I guess you don’t want me to list them here again,
right?
Once we converted our features to standalone components, we have to revisit and refactor the routes in main.ts
to use
the loadComponent
method instead of loadChildren
.
const routes: Routes = [
{
path: 'shows',
loadComponent: () =>
import('./app/features/shows/shows.component').then(
(c) => c.ShowsComponent,
),
},
{
path: 'movies',
loadComponent: () =>
import('./app/features/movies/movies.component').then(
(c) => c.MoviesComponent,
),
},
];
if (environment.production) {
enableProdMode();
}
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)],
});
As it’s name indicates loadComponent
allows us to lazy load a component (standalone) without using a module.
Awesome! We successfully converted our app to standalone components. Furthermore our app is no longer using theRouterModule
instead it now only uses the routers standalone APIs.
Let’s build our app again and inspect the resulting bundle.
Bundle size after converting our app to standalone components and standalone router APIs
The total bundle size is now 233.61 KB
. That’s an overall decrease of 12.62 KB
for the same feature set.🤩 That’s very
nice. But we are particularly interested in the router. So let’s take a closer look.
The routers size is now 60.54 KB
. That’s a decrease of 5.08 KB
. That’s 7.74%
. The 7.74%
match my simplified use case
using source-map-explorer as a measurement tool. I can very well imagine that in more sophisticated scenarios or maybe
also with another measurement tool, you can get up to the 11%
mentioned by Minko.
Summary
The new standalone router APIs are fantastic. They allow us to implement the same awesome features resulting in smaller
bundle sizes.
But be aware that they are currently in developer preview and will be stable in v15. Therefore I wouldn’t use them today
for productive code. Furthermore, they force you to use standalone components throughout your application.
Depending on your application size, a partial migration might be a nice way to migrate your app toward the new
standalone Router APIs. However, keep in mind mixing provideRouter
with RouterModule
should only be an intermediate step
and not the end solution.
Do you enjoy the theme of the code preview? Explore our brand new theme plugin
Skol - the ultimate IDE theme
Northern lights feeling straight to your IDE. A simple but powerful dark theme that looks great and relaxes your eyes.
Prepare yourself for the future of Angular and become an Angular Signals expert today!
Angular Signals Mastercalss eBook
Discover why Angular Signals are essential, explore their versatile API, and unlock the secrets of their inner workings.
Elevate your development skills and prepare yourself for the future of Angular. Get ahead today!
Get notified
about new blog posts
Sign up for Angular Experts Content Updates & News and you'll get notified whenever we release a new blog posts about Angular, Ngrx, RxJs or other interesting Frontend topics!
We will never share your email with anyone else and you can unsubscribe at any time!
Responses & comments
Do not hesitate to ask questions and share your own experience and perspective with the topic
Nivek
Google Developer Expert (GDE)
for Angular & Web Technologies
A trainer, consultant, and senior front-end engineer with a focus on the modern web, as well as a Google Developer Expert for Angular & Web technologies. He is deeply experienced in implementing, maintaining and improving applications and core libraries on behalf of big enterprises worldwide.
Kevin is forever expanding and sharing his knowledge. He maintains and contributes to multiple open-source projects and teaches modern web technologies on stage, in workshops, podcasts, videos and articles. He is a writer for various tech publications and was 2019’s most active Angular In-Depth publication writer.
58
Blog posts
2M+
Blog views
39
NPM packages
4M+
Downloaded packages
100+
Videos
15
Celebrated Champions League titles
You might also like
Check out following blog posts from Angular Experts to learn even more about related topics like Angular !
My new Angular Coding Style
Embracing the New Angular: A Fresh Perspective on how to write Angular code.
Kevin Kreuzer
@kreuzercode
Dec 2, 2024
7 min read
Top 10 Angular Architecture Mistakes You Really Want To Avoid
In 2024, Angular keeps changing for better with ever increasing pace, but the big picture remains the same which makes architecture know-how timeless and well worth your time!
Tomas Trajan
@tomastrajan
Sep 10, 2024
15 min read
Angular Signal Inputs
Revolutionize Your Angular Components with the brand new Reactive Signal Inputs.
Kevin Kreuzer
@kreuzercode
Jan 24, 2024
6 min read
Empower your team with our extensive experience
Angular Experts have spent many years consulting with enterprises and startups alike, leading workshops and tutorials, and maintaining rich open source resources. We take great pride in our experience in modern front-end and would be thrilled to help your business boom