We will examine Angular's newly released standalone Router APIs. Do they bring real value? What impact can we achieve on the Router's bundle size?

emoji_objects emoji_objects emoji_objects
Kevin Kreuzer

Kevin Kreuzer

@nivekcode

Oct 18, 2022

6 min read

Angular Router Standalone APIs
share

Not long ago, a Tweet by Minko Gechev caught my attention.

An 11% reduction is impressive! 😎 Let’s put it to the test.

With a fresh brew in hand, I set off to build a simple app featuring two lazy-loaded routes (/movies and /shows) for browsing films and series.

Click here to get the full source-code

🤫 Pro tip! You can create lazy-loaded feature modules with the use of a simple single command!
Angular Router Standalone APIs - Angular Experts — figure 3

Movies Shows
Angular Router Standalone APIs - Angular Experts — figure 4

Movie and TV show listings in the sample app. (These rankings reflect my personal taste 😉).

So now we end up with a clean application structure: two distinct routes, each backed by its own service providing entries, plus a handful of components that render them. Nothing out of the ordinary.

Now, switch your attention to what really matters here — how large our bundles are.

Initial Bundle size report from the source map explorer
Initial Bundle size report from the source map explorer

Your application comes in at 246.23 KB, with 203.17 KB residing in the main bundle and 65.62 KB attributed to the router. That translates to 26.6% of the overall app size. While 26.6% might seem significant, it’s worth noting that our app is quite simple, which keeps the total size fairly minimal.

With that baseline established, let’s shift our focus to migrating the application to the router’s standalone APIs.

Standalone APIs

To get the most out of the standalone APIs, we should first review the official docs for a complete understanding of how to integrate them.

A new provideRouter function is available in Angular. It’s designed to supply the application with all necessary Router capabilities, including routes and various router features. In the current setup, these functionalities come through RouterModule.

We now have a clear method for providing router functionalities, but what about the components and directives like router-outlet or routerLink? The solution is straightforward: they can be imported. Every router component and directive is now exposed as a standalone API.

That’s promising — the new APIs cover all our needs. So, what’s the proper way to utilize provideRouter?

The provideRouter function can be supplied as an options argument to the bootstrapApplication function.

const appRoutes: Routes = [];
bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(
      appRoutes,
      withDebugTracing(),
      withRouterConfig({ paramsInheritanceStrategy: 'always' }),
    ),
  ],
});

Hold on — bootstrapApplication? That’s new to me. I’ve never worked with it. In my projects, we call bootstrapModule
via the platformBrowserDynamic function.

Version 14 introduced bootstrapApplication, which lets us bootstrap a standalone component.

So does that imply I must switch to standalone components to benefit from the provideRouter
function?

Correct, at least for the AppComponent. Let’s try out two distinct scenarios.

For the first scenario, we’ll turn our AppComoponent into a standalone component, yet keep using the RouterModule
in our lazy-loaded modules. The second scenario involves converting all components in the app to standalone.

Hybrid approach

To use the provideRouter function, we must make our AppComponent standalone by setting
the standalone property to true.

Because our template relies on router-outlet and routerLink, we also need to add the Standalone APIs RouterOutlet and
RouterLinkWithHref to our imports.

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 {}

After migrating your AppComponent to a standalone component, both the app.module.ts and theapp.routing.module.ts files become unnecessary and can be removed. The only remaining task is to update your 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),
});

Our route definitions were relocated out of app.routing.module.ts and into main.ts; the paths were then updated accordingly. Following that, we supply the array to the provideRouter function within the providers property of bootstrapApplication.

Excellent! A portion of the router's standalone APIs was introduced. Let’s check what the application’s bundle size looks like now.

Bundle size of our application that now uses some of the routers standalone APIs as well as the RouterModule.
Bundle size of our application that now uses some of the routers standalone APIs as well as the RouterModule.

The application’s overall bundle now sits at 243.08 KB. What stands out is that while the total size shrank, the router saw a slight increase of 0.13 KB.

The reason is straightforward: we’re pairing provideRouter with RouterModule, which remains in play for our lazy-loaded feature modules.

This setup doesn’t give us the router bundle reduction we’re after. Still, it’s a practical option for those looking to transition incrementally toward fully standalone components.

Full standalone components

Now, let’s take this further. We’ll switch our lazy-loaded routing modules to lazy-loaded standalone components.

First, we’ll delete movies.routing.module.ts and movies.module.ts. Then, we’ll refactor movies.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) {}
}

In the component's decorator, we assign true to the standalone setting and include an imports array holding both MovieCardComponent and NgFor.

Note: I have already taken care of converting MovieCardComponent into a standalone component beforehand.

Naturally, the shows route requires an identical procedure. However, I suspect you'd rather not see me repeat those details yet again.

With our features now acting as standalone components, we must return to main.ts and modify the routes, swapping out loadChildren for the loadComponent function.

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)],
});

Given its name, loadComponent makes it possible to defer loading a standalone component, bypassing the need for a module altogether.

This is great news! We've fully migrated the application to use standalone components. What's more, we've eliminated the
RouterModule dependency, replacing it solely with the router's standalone APIs.

Now, let's rebuild the application and take a closer look at the generated bundle.

Bundle size after converting our app to standalone components and standalone router APIs
Bundle size after converting our app to standalone components and standalone router APIs

At this point, the total bundle size sits at 233.61 KB, which represents a 12.62 KB reduction while delivering the exact same functionality. That's certainly impressive.🤩 But since our focus is on the router, let’s zoom in there.

Currently, the router itself comes in at 60.54 KB, marking a 5.08 KB drop—that’s a 7.74% improvement. This aligns perfectly with what I observed in my simplified setup using source-map-explorer. I can easily see more complex projects, especially those measured with different tools, reaching the 11% gain that Minko reported.

Summary

The standalone router APIs are truly impressive. They let us build the same rich functionality while keeping the final bundle leaner.

However, keep in mind these APIs are still in developer preview and won’t be stable until v15. Because of that, I’d hold off on using them in production today. Also, adopting them requires your entire app to rely on standalone components.

If your application is large, a step-by-step migration could be a sensible approach toward the new standalone Router APIs. Still, remember that mixing provideRouter with RouterModule is fine as a temporary measure, but it shouldn’t be the final state.

Do you enjoy the theme of the code preview? Explore our brand new theme plugin

Skol - the ultimate IDE theme

Skol - the ultimate IDE theme

Bring the aurora experience right into your code editor. A clean yet striking dark theme that is easy on the eyes and visually appealing.

Craft more intelligent interfaces with Angular + AI

Angular + AI Video Course

Angular + AI Video Course

A practical workshop that demonstrates how to bring AI into Angular applications, leveraging Hash Brown for responsive, intelligent interfaces.

Explore real-time streaming chat, tool-assisted interactions, generative UI patterns, and well-defined output schemas, all through guided examples.

Get ready for what’s next in Angular and turn yourself into a Signals pro!

Angular Signals Masterclass eBook

Angular Signals Mastercalss eBook

Find out why Angular Signals have become a necessity, examine the full range of their interactive methods, and dive into the mechanics that power them.

Sharpen your coding expertise and get poised for what lies ahead in Angular. Stay ahead of the curve!

Enjoying this content and eager to conquer Angular's latest Signal Forms?

Angular Signal Forms: A Comprehensive Practical Workshop

Angular Signal Forms: Hands-On Masterclass

Get to grips with Angular's recently introduced Signal-Forms across a dozen step-by-step sections that blend concepts with practical exercises.

Dive into form fundamentals, validation rules, bespoke controls, nested forms, transitioning approaches, and much else besides.

Win win deal illustration

Never miss a post
from our blog

Subscribe to the Angular Experts Content Updates & News and we will let you know the moment a new article on Angular, Ngrx, RxJs, or other exciting Frontend topics goes live.

Your email stays private, and you are free to unsubscribe whenever you like!

Some emails may contain extra promotional material, as outlined in our Privacy policy.

Questions & feedback

Feel free to ask questions and share your insights and personal experiences on the subject

You might also like

Take a look at these additional Angular Experts articles to deepen your understanding of related areas such as Angular !

Top 10 Angular Architecture Mistakes You Really Want To Avoid

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!

emoji_objects emoji_objects emoji_objects
Tomas Trajan

Tomas Trajan

@tomastrajan

Sep 10, 2024

15 min read

Angular Signal Inputs

Angular Signal Inputs

Revolutionize Your Angular Components with the brand new Reactive Signal Inputs.

emoji_objects emoji_objects emoji_objects
Kevin Kreuzer

Kevin Kreuzer

@nivekcode

Jan 24, 2024

6 min read

Improving DX with new Angular @Input Value Transform

Improving DX with new Angular @Input Value Transform

Embrace the Future: Moving Beyond Getters and Setters! Learn how to leverage the power of custom transformers or the build in booleanAttribute and numberAttribute transformers.

emoji_objects emoji_objects emoji_objects
Kevin Kreuzer

Kevin Kreuzer

@nivekcode

Nov 18, 2023

3 min read

Leverage our deep know-how to boost your team

From big corporations to early-stage startups, our consulting, workshops, and open-source contributions have supported a wide range of projects. We’re proud of our front-end expertise and excited to help drive your success