Lazy Loading Angular Standalone Component
Lazy Loading Angular Standalone Component

In this post I want to talk about standalone components and lazy routing.

Not so long ago we got standalone components in Angular which means that we can create components without modules. And obviously people have lots of questions about correct usage of standalone components.

In this post I want to answer correctly how you can implement standalone components with lazy loading.

Registering in other components

The easiest approach to use standalone components is just to import them in place where we want to use them. Let's say that we want to render FooComponent inside our AppComponent.

@Component({
  ...
  imports: [..., FooComponent],
})
export class AppComponent {}
<app-foo></app-foo>

Here we imported FooComponent inside our AppComponent and rendered it on the screen.

Importing

As you can see in browser our Foo component was successfully rendered on the screen. So if we don't talk about routing we can simply inject needed component and render it.

Registering in routes

But what about routing? What should we do if we want to render our standalone component on the route? In this case we must look on app.routing file. This is the single place where we store all our routes for the application.

In order to understand from what file we register all routes we must look in main.ts.

import { routes } from './app/app-routing.module';

bootstrapApplication(AppComponent, { providers: [provideRouter(routes)] });

As you can see in provideRouter we put routes from the app-routing file.

import { Routes } from '@angular/router';

export const routes: Routes = [];

For now my routes are completely empty.

Previously we could register routes inside modules and just inject modules but it doesn't work like this with standalone components.

We have just standalone components and we can't define routing inside them. Which means that we need to register component with route in our app.routing file.

export const routes: Routes = [
  {
    path: 'foo',
    component: FooComponent
  },
];

Routing

As you can see we can jump to /foo route and our FooComponent is rendered here.

Lazy loading in routing

But at some point we for sure want to implement lazy routes. Why is that? Lazy routes help us to split our application in different chunks so your application will be loading faster because we will load only chunks which are needed for specific URL.

Let's say that we want to lazy load our FooComponent.

import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: 'foo',
    loadComponent: () => import('./foo/foo.component').then((m) => m.FooComponent),
  },
];

Here we lazy loaded FooComponent and it looks super similar to the way how we lazy loaded modules previously. It is so similar because essentially any standalone component is a mix of module and component.

Lazy

Our /foo route works just like before but now in network you can see that our FooComponent is being loaded as an additional chunk and it is not bundled together with the whole application.

Isolating features

The previous variant is totally fine but I have even better variant which I prefer to use in real projects. Essentially when we create our components we want to pack everything inside and isolate them as a feature. If it is a big feature you might pack inside several components, store, actions, reducers and services. So we want to isolate everything completely.

In order to use something which is similar to module we can create routes file.

// src/app/foo/foo.routes.ts
import { Route } from '@angular/router';
import { FooComponent } from './foo.component';

export const routes: Route[] = [
  {
    path: '',
    component: FooComponent,
  },
];

Here we defined all routes which are related to foo feature. Also you can see that the path is an empty string because all paths are related to this feature and are not defined globally.

Now we just need to make small adjustments to our global routes.

// src/app/app-routing.ts
import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: 'foo',
    loadChildren: () => import('./foo/foo.routes').then((m) => m.routes),
  },
];

Here we used loadChildren instead of loadComponent. Also we loaded here a route from our routes file and not a standalone component directly.

This approach allows us to register any dependencies which are needed only inside our foo feature on a route level and not globally.

export const routes: Route[] = [
  {
    path: '',
    component: FooComponent,
    providers: [provideEffects(), provideState(), FooService],
  },
];

Here we can register state, effects, service inside our foo.routes.ts without polluting our main.ts.

And actually if you want to learn Angular with NgRx from empty folder to a fully functional production application make sure to check my Angular and NgRx - Building Real Project From Scratch course.

📚 Source code of what we've done