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

Oct 18, 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 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!

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

Kevin Kreuzer

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

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 !

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

@kreuzercode

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

@kreuzercode

Nov 18, 2023

3 min read

Angular Material components testing

Angular Material components testing

How and why to use Angular Materials component harness to write reliable, stable and readable component tests

emoji_objects emoji_objects emoji_objects
Kevin Kreuzer

Kevin Kreuzer

@kreuzercode

Feb 14, 2023

7 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

or