← Back to Articles         
Angular Lazy Loading: Before vs Standalone

Angular Lazy Loading: Before vs Standalone

Published on 02 Oct 2026     10 min read Angular
Older Angular vs Modern Angular

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, while loadChildren() 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 ProductsModule dynamically when the /products route 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 Products component when the /products route 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 with loadComponent(), while loadChildren() remains useful for lazy-loading a group of standalone routes.

← Back to Articles