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.
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
},
];
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.
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
.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done