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?

emoji_objects emoji_objects emoji_objects
Kevin Kreuzer

Kevin Kreuzer

@kreuzercode

18.10.2022

6 min read

Angular Router Standalone APIs
share

A couple of days ago, I came across this Tweet from Minko Gechev.

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.

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!

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
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 as
router-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 and
RouterLinkWithHref.

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 the
app.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 the
provideRouter 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.
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 the
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) {}
}

We set standalone in the components decorator to true and add an imports array that contains a value of
MovieCardComponent 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 the
RouterModule 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
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

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.

Do you enjoy the content and would like to learn more about how to ensure long term maintainability of your Angular application?

Angular Enterprise Architecture eBook

Angular Enterprise Architecture eBook

Learn how to architect a new or existing enterprise grade Angular application with a bulletproof tooling based automated architecture validation.

This will ensure that Your project stays maintainable, extendable and therefore with high delivery velocity over the whole project lifetime!

Get notified
about new blog posts

Sign up for Angular Experts Content Updates & News and you'll get notified whenever I release a new article 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!

Emails may include additional promotional content, for more details see our Privacy policy.
Kevin Kreuzer - GDE for Angular & Web Technologies

Kevin Kreuzer

GDE for Angular & Web Technologies

Trainer, Berater und Senior Front-End Engineer mit Schwerpunkt auf dem modernen Web. Er ist sehr erfahren in der Implementierung, Wartung und Verbesserung von Anwendungen und Kernbibliotheken für große Unternehmen.

Kevin ist ständig dabei, sein Wissen zu erweitern und zu teilen. Er unterhält mehrere Open-Source-Projekte, unterrichtet moderne Webtechnologie in Workshops, Podcasts, Videos und Artikeln. Weiter ist er ein beliebter Referent auf Konferenzen. Er schreibt für verschiedene Tech-Publikationen und war 2019 der aktivste Autor der beliebten Angular In-Depth Publikation.

58

Blog posts

2M

Blog views

23

NPM packages

3M+

Downloaded packages

39

Videos

14

Celebrated Champions League titles

Responses & comments

Do not hesitate to ask questions and share your own experience and perspective with the topic

You might also like

Check out following blog posts from Angular Experts to learn even more about related topics like Angular !

Stärken Sie Ihr Team mit unserer umfassenden Erfahrung

Unsere Angular Experten haben viele Jahre damit verbracht, Unternehmen und Startups zu beraten, Workshops und Tutorials zu leiten und umfangreiche Open-Source-Ressourcen zu pflegen. Wir sind sehr stolz auf unsere Erfahrung im Bereich des modernen Frontends und würden uns freuen auch Ihrem Unternehmen zum Aufschwung zu verhelfen.

or