Angular Lazy Loading: Before vs Standalone
The major change is:
BEFORE NOW
------ ---
Feature NgModule Standalone component/routes
loadChildren() loadComponent()
or or
loadChildren() loadChildren()
↓ ↓
Lazy-load module Lazy-load component/routes
A very important interview point:
loadChildren()did not disappear with standalone Angular.loadComponent()is commonly used to lazy-load a single standalone component, whileloadChildren()can lazy-load a group of standalone routes.
1. How Were Lazy-Loaded Feature Modules Implemented?
What is lazy loading?
Lazy loading means loading application code only when it is needed, instead of downloading all feature code when the application first starts.
Suppose the application has:
Home
Products
Admin
Reports
If the user initially visits only:
/
there may be no need to immediately load the Admin feature.
Instead:
Application starts
↓
Home loaded
User visits /admin
↓
Admin code downloaded
↓
Admin feature displayed
This reduces the initial JavaScript bundle and can improve startup performance.
Traditional NgModule-Based Lazy Loading
Before standalone APIs, large applications commonly organized features into feature NgModules.
For example:
src/app/
├── app.module.ts
├── app-routing.module.ts
│
├── home/
│
└── products/
├── products.module.ts
├── products-routing.module.ts
├── product-list.component.ts
└── product-details.component.ts
Here:
ProductsModule
was a feature module.
The entire module could be lazy-loaded.
Full Old-Style Example
Suppose we have:
/products
/products/1
and want the Products feature loaded only when the user visits /products.
app-routing.module.ts
import { NgModule } from '@angular/core';
import {
RouterModule,
Routes
} from '@angular/router';
import {
HomeComponent
} from './home/home.component';
const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'products',
loadChildren: () =>
import('./products/products.module')
.then(m => m.ProductsModule)
}
];
@NgModule({
imports: [
RouterModule.forRoot(routes)
],
exports: [
RouterModule
]
})
export class AppRoutingModule {
}
The important code was:
loadChildren: () =>
import('./products/products.module')
.then(m => m.ProductsModule)
This told Angular:
Don't include the Products feature in the initial route path. Load
ProductsModuledynamically when the/productsroute is needed.
products.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
ProductsRoutingModule
} from './products-routing.module';
import {
ProductListComponent
} from './product-list.component';
import {
ProductDetailsComponent
} from './product-details.component';
@NgModule({
declarations: [
ProductListComponent,
ProductDetailsComponent
],
imports: [
CommonModule,
ProductsRoutingModule
]
})
export class ProductsModule {
}
This was the lazy-loaded feature module.
products-routing.module.ts
import { NgModule } from '@angular/core';
import {
RouterModule,
Routes
} from '@angular/router';
import {
ProductListComponent
} from './product-list.component';
import {
ProductDetailsComponent
} from './product-details.component';
const routes: Routes = [
{
path: '',
component: ProductListComponent
},
{
path: ':id',
component: ProductDetailsComponent
}
];
@NgModule({
imports: [
RouterModule.forChild(routes)
],
exports: [
RouterModule
]
})
export class ProductsRoutingModule {
}
Notice:
RouterModule.forChild(routes)
was used for the feature module's routes.
Old Lazy Loading Flow
When the application starts:
Browser
↓
main.ts
↓
AppModule
↓
AppRoutingModule
↓
Home
The Products module is not needed yet.
When the user visits:
/products
Angular sees:
loadChildren: () =>
import('./products/products.module')
.then(m => m.ProductsModule)
Then:
/products
↓
Angular Router
↓
loadChildren()
↓
Dynamic import()
↓
ProductsModule downloaded
↓
ProductsRoutingModule
↓
ProductListComponent
Why loadChildren()?
Because Angular was loading a child routing configuration packaged inside a feature NgModule.
loadChildren
↓
ProductsModule
↓
ProductsRoutingModule
↓
Child routes
Why Dynamic import()?
This part:
import('./products/products.module')
is a JavaScript dynamic import.
It allows the build system to create a separate code chunk.
Conceptually:
Initial build
main.js
styles.css
...
products chunk
The products chunk doesn't need to execute until the corresponding lazy route is requested.
2. How Are Standalone Components Lazy-Loaded?
With standalone Angular, we don't need to create a feature NgModule simply to lazy-load a component.
For a single standalone route component, we can use:
loadComponent()
Simple Modern Example
Suppose we have:
/
↓
Home
/products
↓
Products
We want Products to be lazy-loaded.
app.routes.ts
import { Routes } from '@angular/router';
import { Home } from './home';
export const routes: Routes = [
{
path: '',
component: Home
},
{
path: 'products',
loadComponent: () =>
import('./products/products')
.then(m => m.Products)
}
];
The important code is:
loadComponent: () =>
import('./products/products')
.then(m => m.Products)
This means:
Load the
Productscomponent when the/productsroute is requested.
products.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-products',
template: `
<h2>Products</h2>
<p>Products loaded successfully.</p>
`
})
export class Products {
}
No:
ProductsModule
is required.
No:
ProductsRoutingModule
is required for this simple route.
Modern Lazy Loading Flow
Application starts
↓
app.routes.ts
↓
Home displayed
User visits /products
↓
Angular Router
↓
loadComponent()
↓
dynamic import()
↓
Products chunk downloaded
↓
Products component created
↓
Displayed
Eager Loading vs Lazy Loading
Consider:
import { Products } from './products/products';
export const routes: Routes = [
{
path: 'products',
component: Products
}
];
This uses a static import:
import { Products } from './products/products';
Conceptually, Products is eagerly referenced by the route configuration.
Compare that with:
{
path: 'products',
loadComponent: () =>
import('./products/products')
.then(m => m.Products)
}
The dynamic import provides a lazy-loading boundary.
component vs loadComponent
Eager
{
path: 'products',
component: Products
}
requires:
import { Products } from './products/products';
Lazy
{
path: 'products',
loadComponent: () =>
import('./products/products')
.then(m => m.Products)
}
Easy way to remember:
component:
↓
Component referenced directly
loadComponent:
↓
Component loaded lazily
What If a Feature Has Multiple Pages?
This is where an important distinction appears.
Suppose Products has:
/products
/products/:id
/products/create
/products/edit/:id
You could lazy-load every component individually, but Angular also allows you to lazy-load a set of routes.
This still uses:
loadChildren()
even though there is no feature NgModule.
Modern loadChildren() With Standalone Routes
Consider:
products/
├── products.routes.ts
├── product-list.ts
├── product-details.ts
├── product-create.ts
└── product-edit.ts
app.routes.ts
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: 'products',
loadChildren: () =>
import('./products/products.routes')
.then(m => m.PRODUCT_ROUTES)
}
];
Notice:
loadChildren()
is still being used.
But it is not loading ProductsModule.
It is loading:
PRODUCT_ROUTES
products.routes.ts
import { Routes } from '@angular/router';
import { ProductList } from './product-list';
import { ProductDetails } from './product-details';
import { ProductCreate } from './product-create';
export const PRODUCT_ROUTES: Routes = [
{
path: '',
component: ProductList
},
{
path: 'create',
component: ProductCreate
},
{
path: ':id',
component: ProductDetails
}
];
Now:
/products
↓
loadChildren()
↓
products.routes.ts
↓
PRODUCT_ROUTES
↓
┌────┼─────────┐
↓ ↓ ↓
List Create Details
This is a very useful pattern for larger standalone applications.
Can We Combine loadChildren() and loadComponent()?
Yes.
For example:
app.routes.ts
{
path: 'products',
loadChildren: () =>
import('./products/products.routes')
.then(m => m.PRODUCT_ROUTES)
}
Then inside:
products.routes.ts
import { Routes } from '@angular/router';
export const PRODUCT_ROUTES: Routes = [
{
path: '',
loadComponent: () =>
import('./product-list')
.then(m => m.ProductList)
},
{
path: 'create',
loadComponent: () =>
import('./product-create')
.then(m => m.ProductCreate)
},
{
path: ':id',
loadComponent: () =>
import('./product-details')
.then(m => m.ProductDetails)
}
];
This gives multiple lazy boundaries.
loadComponent() vs loadChildren()
This is a common interview question.
loadComponent() |
loadChildren() |
|---|---|
| Lazy-loads a route component | Lazy-loads child route configuration |
| Excellent for one page/component | Excellent for a feature with multiple routes |
| Common with standalone components | Works with standalone route groups and historically feature NgModules |
| Returns a component | Returns routes/module depending on pattern |
Easy rule:
One lazy page
↓
loadComponent()
Feature containing multiple routes
↓
loadChildren()
This is a useful guideline rather than an absolute restriction.
Before vs Now
Before Standalone
app-routing.module.ts
↓
loadChildren()
↓
ProductsModule
↓
ProductsRoutingModule
↓
Product components
Example:
{
path: 'products',
loadChildren: () =>
import('./products/products.module')
.then(m => m.ProductsModule)
}
Modern Standalone — Single Component
app.routes.ts
↓
loadComponent()
↓
Products
{
path: 'products',
loadComponent: () =>
import('./products/products')
.then(m => m.Products)
}
Modern Standalone — Feature Routes
app.routes.ts
↓
loadChildren()
↓
products.routes.ts
↓
PRODUCT_ROUTES
↓
Product pages
{
path: 'products',
loadChildren: () =>
import('./products/products.routes')
.then(m => m.PRODUCT_ROUTES)
}
Example for Angular + ASP.NET Core Application
For a larger application, you could organize the Angular frontend like:
src/app/
├── app.ts
├── app.config.ts
├── app.routes.ts
│
├── public/
│ ├── home/
│ ├── articles/
│ └── contact/
│
└── admin/
├── admin.routes.ts
├── dashboard/
├── articles/
├── categories/
└── tags/
Then lazy-load the entire Admin route configuration:
export const routes: Routes = [
{
path: 'admin',
loadChildren: () =>
import('./admin/admin.routes')
.then(m => m.ADMIN_ROUTES)
}
];
Then:
export const ADMIN_ROUTES: Routes = [
{
path: 'dashboard',
loadComponent: () =>
import('./dashboard/dashboard')
.then(m => m.Dashboard)
},
{
path: 'articles',
loadComponent: () =>
import('./articles/articles')
.then(m => m.Articles)
},
{
path: 'categories',
loadComponent: () =>
import('./categories/categories')
.then(m => m.Categories)
}
];
Conceptually:
Angular starts
↓
Public application available
↓
User opens /admin
↓
Admin route chunk loaded
↓
Admin route selected
↓
Dashboard / Articles / Categories
This is a clean standalone structure for a substantial Angular + ASP.NET Core application.
Key Points
Lazy loading
Lazy loading means:
Load application code only when that route/feature is needed.
It helps reduce the amount of JavaScript required during initial application startup.
Before standalone Angular
Lazy loading was commonly based on feature NgModules:
loadChildren: () =>
import('./products/products.module')
.then(m => m.ProductsModule)
Architecture:
loadChildren
↓
Feature NgModule
↓
Feature Routing Module
↓
Components
Standalone component
A single standalone component can be lazy-loaded with:
loadComponent: () =>
import('./products/products')
.then(m => m.Products)
Standalone route group
A group of routes can still be lazy-loaded with:
loadChildren: () =>
import('./products/products.routes')
.then(m => m.PRODUCT_ROUTES)
Therefore:
loadChildren()is not obsolete.
It has uses in both older NgModule-based routing and modern standalone route-based applications.
Interview Questions and Answers
1. What is lazy loading?
Lazy loading means loading feature code only when the user navigates to the corresponding route, rather than loading everything during initial application startup.
2. How were features lazy-loaded before standalone Angular?
They were commonly organized into feature NgModules and loaded using loadChildren().
loadChildren: () =>
import('./products/products.module')
.then(m => m.ProductsModule)
3. What was a lazy-loaded feature module?
It was an NgModule containing a feature's components, dependencies and usually its routing configuration.
For example:
ProductsModule
ProductsRoutingModule
ProductListComponent
ProductDetailsComponent
4. What did RouterModule.forChild() do?
It registered routes belonging to a feature module.
RouterModule.forChild(routes)
It was commonly used inside feature routing modules.
5. How do you lazy-load a standalone component?
Use:
loadComponent()
Example:
{
path: 'products',
loadComponent: () =>
import('./products/products')
.then(m => m.Products)
}
6. What is loadComponent()?
loadComponent() allows the Angular Router to dynamically load a route component when that route is requested.
loadComponent: () =>
import('./products')
.then(m => m.Products)
7. Is loadChildren() still used with standalone Angular?
Yes.
It can lazy-load a group of standalone routes:
loadChildren: () =>
import('./products/products.routes')
.then(m => m.PRODUCT_ROUTES)
8. What is the difference between loadComponent() and loadChildren()?
loadComponent() lazy-loads a route component, while loadChildren() lazy-loads child route configuration.
A useful rule is:
Single lazy page
↓
loadComponent()
Feature with multiple routes
↓
loadChildren()
9. What is the difference between component and loadComponent?
component directly references a component:
{
path: 'products',
component: Products
}
loadComponent dynamically imports it:
{
path: 'products',
loadComponent: () =>
import('./products')
.then(m => m.Products)
}
The second form creates a lazy-loading boundary.
10. Why is lazy loading useful?
It can:
- Reduce the initial JavaScript bundle
- Reduce initial download and parsing work
- Improve startup performance
- Keep large feature areas separated
- Avoid loading code the user may never visit
Interview Quick Revision
BEFORE STANDALONE
=================
Feature NgModule
↓
loadChildren()
↓
ProductsModule
↓
ProductsRoutingModule
↓
Components
MODERN — SINGLE PAGE
====================
Standalone Component
↓
loadComponent()
↓
Products
MODERN — FEATURE
================
Standalone Routes
↓
loadChildren()
↓
products.routes.ts
↓
PRODUCT_ROUTES
One-Line Interview Answer
Before standalone Angular, lazy loading was commonly implemented by loading feature NgModules with
loadChildren(). In modern standalone Angular, a single standalone route component can be lazy-loaded withloadComponent(), whileloadChildren()remains useful for lazy-loading a group of standalone routes.