Have you ever loaded up your Angular app and something feels off? For some reason it’s loading just a little too slow, even though the code’s solid and the design looks good - so what gives?
Well, chances are that your app is working harder than it needs to. Possibly even loading things users haven’t even asked for yet and making them wait for what they actually want.
Not great right?
Especially when you consider that if someone has to wait more than 3 seconds online, they’ll probably stop what they’re doing and do something else.
The good news is that this is all fixable. In fact, we can use 2 different features that are built into Angular and can seriously improve performance. And in this guide, I’ll show you how to use each of them, explain when to use one over the other, and help you avoid common mistakes along the way.
Sidenote: If you want to learn more and improve your Angular skills, then check out my complete Angular Developer course:
This is the only Angular course you need to learn Angular, build enterprise-level applications from scratch, and get hired as an Angular Developer. Completely revamped for 2025!
With that out of the way, let’s get into this guide.
Lazy loading might sound like a fancy technical term, but the concept is actually really simple. It’s just a smarter way to manage when and how your app loads different parts of itself.
So here’s the deal:
When you first start building an app, it’s probably small with just a few features. So you can be lazy (no pun intended) when you design because you can kind of brute force load times.
It’s less stuff to load so you can let it all load at once. We call this eager loading.
However, as your Angular app grows, it’s probably going to get a lot of features, routes, and components. And so now if you try to load all of that upfront, the app gets weighed down.
This means that even if a user only needs a single page, they’re stuck waiting while everything loads behind the scenes.
Not great right?
The solution to this is lazy loading. Rather than loading everything at once, Angular waits until a user actually needs a feature before it loads the code for it.
For example
If someone visits your homepage, they only get the code for the homepage. If they later click into the dashboard, then and only then does Angular go fetch the code for the dashboard.
It’s a bit like only packing what you need for today’s trip, rather than dragging around a full suitcase just in case you might use something later.
It seems simple because it is, but this small shift can make a huge difference in performance. The app loads faster, it feels more responsive, and users don’t get stuck waiting on things they’re not even using yet.
The question of course is how do we do this?
Now that you’ve got the concept, let’s talk about how lazy loading actually works in Angular.
At a high level, Angular gives you two main ways to lazy load parts of your app. You can lazy load entire modules or individual standalone components. Both approaches are built into the Angular Router, and which one you use depends on how your app is structured.
If your app uses feature modules — and most Angular apps still do — you’ll probably use the loadChildren
method. This tells Angular, “Don’t load this module until the user navigates to this route.” Super handy when you’ve got different sections like an admin panel, user dashboard, or settings area that don’t need to be available right away.
But if your app is built using standalone components, which became available in Angular 14 and got even better in later versions, you can use the newer loadComponent
method. This lets you skip creating a full module just to lazy load a feature. It’s a bit more lightweight and modern, especially for smaller apps or micro frontends.
In both cases, Angular listens to the router. When the user navigates to a route you’ve set up for lazy loading, Angular grabs the code it needs and brings it in on demand.
It’s worth knowing that this isn’t just some fancy trick. Under the hood, Angular is working with something called code splitting. That’s a bundler feature (from tools like Webpack or Vite) that breaks your app into separate chunks. When you configure lazy loading, Angular sets up those chunks so they can be loaded independently — meaning faster load times and better performance for users.
In the next section, we’ll start with the more traditional approach of lazy loading modules using loadChildren
. Then we’ll look at the newer way to lazy load standalone components with loadComponent
.
So that by the end of both, you’ll know how to do this no matter how your app is structured and when to use each method.
If your Angular app is built using feature modules, this is the most common way to implement lazy loading.
Here’s how it works:
Instead of importing everything up front in your main AppModule
, you split your features into separate modules, like AdminModule
, UserModule
, or DashboardModule
. Then, in your routing setup, you tell Angular to load those modules only when the user navigates to their routes.
This is exactly what loadChildren
is for.
Let’s walk through it step by step.
First, you generate a module and component for your feature.
For example:
ng generate module dashboard --route dashboard --module app
This command actually does most of the work for you. It sets up a DashboardModule
, creates a DashboardComponent
, and wires everything up for lazy loading in your AppRoutingModule
.
But if you’re doing it manually, it looks something like this:
// dashboard-routing.module.ts
const routes: Routes = [
{ path: '', component: DashboardComponent }
];
// app-routing.module.ts
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule)
}
];
Notice how we’re not importing the DashboardModule
at the top of the file. Instead, we’re using a dynamic import()
to pull it in only when needed.
When a user visits /dashboard
, Angular sees that the route uses loadChildren
, so it fetches the dashboard.module.ts
chunk from the server and loads it on the fly. If they never visit that route, the module never gets loaded.
That’s lazy loading in action — it reduces the amount of JavaScript downloaded during the initial app load, which means faster startup times and a better user experience.
###*3. A quick reminder on structure
Your lazy-loaded module needs its own routing module (DashboardRoutingModule
) and should declare its own components. It should not be imported into AppModule
— that would defeat the whole purpose of lazy loading.
If you’re building your Angular app using standalone components — which is fully supported as of Angular 14 and beyond — you can skip modules altogether and lazy load components directly. This approach is cleaner, faster to set up, and often makes more sense in smaller apps or modern frontend architectures.
Instead of telling Angular to load an entire module, you just tell it to load a single component when the route is triggered. It’s a more streamlined way to build apps, especially now that Angular doesn’t require everything to live inside a module.
Let’s look at how to do it.
You can generate a standalone component like this:
ng generate component dashboard --standalone
This creates a DashboardComponent
that’s not tied to any module. You’ll see that it includes the standalone: true
flag in the component metadata:
@Component({
standalone: true,
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent { }
loadComponent
Now, instead of using loadChildren
, you’ll use loadComponent
in your routing configuration:
// app-routing.module.ts
const routes: Routes = [
{
path: 'dashboard',
loadComponent: () =>
import('./dashboard/dashboard.component').then(m => m.DashboardComponent)
}
];
Just like with lazy-loaded modules, Angular will wait to load this component until the user navigates to /dashboard
.
The real power of loadComponent
is simplicity, because there’s no need to create a module, set up separate routing, or wire up anything beyond the component itself. This means that if your app doesn’t need module-level organization, or if you want to keep things lightweight, then this method can save time and reduce boilerplate.
That said, you can also mix and match. Some apps still use modules for larger sections, and standalone components for smaller, self-contained features.
Now that you’ve seen both methods, you might be wondering which one should you actually use?
Here’s a simple way to think about it:
Use loadChildren
if:
This is the most common setup in Angular apps, especially in enterprise-scale projects. Modules are helpful for organizing code, grouping related components, and managing things like shared services.
Use loadComponent
if:
This approach is newer and becoming more popular. It’s faster to scaffold, easier to reason about in smaller projects, and fits well with Angular’s push toward modular simplicity in recent versions.
Yes, and in many cases, you should.
You might lazy load entire sections of your app with loadChildren
, and then use loadComponent
inside those sections for smaller features or routes that don’t need their own module. Angular supports both, and they play well together.
You just need to make sure you use them correctly…
Lazy loading can speed up your Angular app and keep your codebase clean, but it’s also easy to make mistakes - especially when you’re just getting started. So before you run off and start breaking your app into chunks, here are a few things to keep in mind.
AppModule
This is the number one mistake beginners make.
If you import a feature module into AppModule
, Angular will load it immediately, even if you’ve set it up with loadChildren
.
Why does this matter?
Simply because by doing this, it completely defeats the point of lazy loading, because it’s now part of the initial bundle and will slow down the load speed.
Remember: If you want Angular to lazy load a module, let the router handle it. Don’t import it manually anywhere else.
Each lazy-loaded module should manage its own components, services, and routes. Think of it as its own mini app inside your main app. That separation helps with performance and also makes your code easier to manage.
It also means any shared services should live in a shared module or be provided in root
, not directly in a lazy-loaded module, unless you want them to have their own instance.
Don’t just break up your app into modules for the sake of it. Lazy loading works best when you apply it to routes that aren’t immediately needed.
For example:
The goal is to keep the initial load small, so your users get a fast, smooth experience from the start.
If your lazily loaded routes include heavy components or third-party libraries, there might be a short delay the first time they load. During that time, the user could see a blank space or layout jump and click on the wrong thing.
To avoid that, you can:
canLoad
to delay navigation until the chunk is readyIt doesn’t need to be fancy. Even a simple “Loading…” message goes a long way to providing a better user experience and not having a CLS issue.
Chances are, you may be working in an application where all the routes are eager loaded. If that’s the case, you may have the idea of lazy loading some of the routes, but you may be hesitant because there are simply too many routes. It’s not fun going through dozens of files updating routes.
Luckily, Angular offers a single command to allow you to convert existing routes into lazy loaded routes.
ng generate @angular/core:route-lazy-loading
This command is going to affect all routes, so be careful before running it. If you only want to convert a few routes, you can specify them with the following command:
ng generate @angular/core:route-lazy-loading --path src/app/sub-component
When providing a path, it must be relative to your application’s root.
Lazy loading is one of the easiest ways to make your Angular apps faster and more efficient, and you’ve already seen how simple it can be.
Now it’s your turn to put it into practice.
Look at your current project. Find one feature, one route, or one component that doesn’t need to be loaded upfront. Try breaking it out into a lazy-loaded module or standalone component.
Start small. Test it out. See the difference in load time and responsiveness. Once you’ve done it once, it’ll start to click and you’ll find more opportunities to optimize as you go.
Remember, if you struggle with any of the concepts in this guide, or want to take a deep dive into Angular, check out my complete Angular Developer course:
This is the only Angular course you need to learn Angular, build enterprise-level applications from scratch, and get hired as an Angular Developer.
Want a sneak peak?
I went ahead and uploaded the first 4 hours of the course below for free:
Plus, once you join, you'll have the opportunity to ask questions in our private Discord community from me, other students and working professionals.
Not only that, but as a member - you’ll also get access to every course in the Zero To Mastery library!
Check out some of my other Angular articles, guides and cheatsheets!