Angular router and lazy loading
How to make your Angular apps faster, cleaner, and easier to manage.
Table of Contents
- Introduction
- Why Routing Matters
- Setting Up Angular Routes
- What Is Lazy Loading?
- How to Implement Lazy Loading in Angular
- Creating a Feature Module
- Lazy Loading with
loadChildren
- Shared Module vs Core Module
- Route Guards (Bonus)
- Best Practices
Introduction
Routers are more than navigations. Especially Angular’s router. It’s how you split your app into manageable chunks, how you optimize performance and how you protect certain parts of your app.
And with lazy loading? You don’t just navigate — you speed up your app. You get performance wins. You scale without chaos.
Let’s see it one by one.
First, Why Routing Matters
In a typical app, never mind a real-world one, you’ll have many pages:
- Dashboard
- Login
- Settings
- Admin Panel
Without a router, you’d be hardcoding logic and jumping between components manually.
With the router? You map URLs to components. You make things feel like a real app.
// app-routing.module.ts
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'login', component: LoginComponent }
];
This is basic routing. But what if AdminModule is huge? You don’t want to load it upfront
What Is Lazy Loading?
Lazy loading means: “Don’t load it until you absolutely need it.”
That means, instead of loading everything at the start, Angular loads pieces of your app when you navigate to them.
Why?
- Faster initial load
- Better UX
- You can scale without killing performance
In Angular, lazy loading works at the module level using the loadChildren property.
Setting Up Angular Routes
Let's start with a basic Angular app.
ng new angular-lazy-demo --routing --style=scss
cd angular-lazy-demo
You’ll now see an app-routing.module.ts.
That’s where your top-level routes go.
How to Implement Lazy Loading in Angular
Let’s say we want to lazy load a DashboardModule
.
Step 1: Generate the module
ng generate module features/dashboard --route dashboard --module app.module
The --route
flag automatically sets up lazy loading.
// app-routing.module.ts
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () =>
import('./features/dashboard/dashboard.module').then(m => m.DashboardModule)
}
];
That’s it. Lazy loading set up. Just like that!
Creating a Feature Module
Let’s peek inside a feature module like DashboardModule
.
// dashboard-routing.module.ts
const routes: Routes = [
{ path: '', component: DashboardHomeComponent },
{ path: 'stats', component: DashboardStatsComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class DashboardRoutingModule {}
Inside dashboard.module.ts
, you should import this routing module.
This structure helps you build isolated, lazy-loadable modules.
Lazy Loading with loadChildren
Here’s what’s happening under the hood:
flowchart TD
A[User visits /dashboard] --> B[Angular Router sees lazy route]
B --> C[Downloads dashboard.module.js]
C --> D[Renders DashboardComponent]
Angular waits until the route is activated before downloading the corresponding module.
Shared Module vs Core Module
As you grow, you'll want to reuse components (buttons, cards, pipes).
SharedModule: for reusable UI components
CoreModule: for singleton services (auth, API, guards)
Never lazy load the CoreModule
.
Structure your app like:
app/
│
├── core/
│ └── core.module.ts
├── shared/
│ └── shared.module.ts
├── features/
│ ├── dashboard/
│ └── settings/
Route Guards (Bonus)
You can guard lazy-loaded routes too.
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
canLoad: [AuthGuard],
canActivate: [AuthGuard]
}
canLoad
: prevents the module from even being downloadedcanActivate
: stops navigation even if module is loaded
Best Practices
✅ Always lazy load feature modules
✅ Use forChild inside feature routing modules
❌ Don’t declare the same component in multiple modules
✅ Keep routes flat (avoid deep nesting unless necessary)
❌ Never lazy load CoreModule