← Back to Articles         
Lazy Loading: NgModules vs Standalone Components

Lazy Loading: NgModules vs Standalone Components

Published on 30 Sep 2026     7 min read Angular
Routing

Lazy Loading: NgModules vs Standalone Components

The main difference is:

Older NgModule approach
        ↓
Lazy load a Feature Module
        ↓
loadChildren()


Modern Standalone approach
        ↓
Lazy load a Component
        ↓
loadComponent()

OR

Lazy load a group of Routes
        ↓
loadChildren()

1. How Was Lazy Loading Implemented with NgModules?

What is it?

Before standalone components, Angular applications were commonly organized using NgModules.

For a large feature such as Products, we might have:

AppModule
│
└── ProductsModule
      ├── ProductListComponent
      ├── ProductDetailsComponent
      └── ProductsRoutingModule

Instead of loading ProductsModule when the application starts, Angular could lazy load it when the user navigated to:

/products

This was configured using:

loadChildren()

Full Simple Sample Program

Project structure

app/
│
├── app.module.ts
├── app-routing.module.ts
│
└── products/
     ├── products.module.ts
     ├── products-routing.module.ts
     ├── product-list.component.ts
     └── product-details.component.ts

product-list.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-product-list',
  template: `
    <h2>Product List</h2>

    <ul>
      <li>Laptop</li>
      <li>Mobile</li>
      <li>Keyboard</li>
    </ul>
  `
})
export class ProductListComponent {
}

product-details.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-product-details',
  template: `
    <h2>Product Details</h2>
  `
})
export class ProductDetailsComponent {
}

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 {
}

products.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { ProductListComponent } from './product-list.component';
import { ProductDetailsComponent } from './product-details.component';
import { ProductsRoutingModule } from './products-routing.module';

@NgModule({
  declarations: [
    ProductListComponent,
    ProductDetailsComponent
  ],
  imports: [
    CommonModule,
    ProductsRoutingModule
  ]
})
export class ProductsModule {
}

app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  {
    path: 'products',
    loadChildren: () =>
      import('./products/products.module')
        .then(m => m.ProductsModule)
  }
];

@NgModule({
  imports: [
    RouterModule.forRoot(routes)
  ],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule {
}

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  bootstrap: [
    AppComponent
  ]
})
export class AppModule {
}

Explanation

The most important code is:

{
  path: 'products',
  loadChildren: () =>
    import('./products/products.module')
      .then(m => m.ProductsModule)
}

Notice that ProductsModule is not statically imported into AppModule.

When the user navigates to:

/products

the flow is:

User visits /products
        ↓
AppRoutingModule
        ↓
loadChildren()
        ↓
import ProductsModule
        ↓
ProductsModule
        ↓
ProductsRoutingModule
        ↓
RouterModule.forChild(routes)
        ↓
ProductListComponent

For:

/products/10

the feature route:

{
  path: ':id',
  component: ProductDetailsComponent
}

matches and displays ProductDetailsComponent.

Important

If you wanted the module to remain lazy loaded, you would not also add:

imports: [
  ProductsModule
]

to AppModule.

Doing so would make the feature part of the eager application dependency graph.


2. How Is Lazy Loading Implemented with Standalone Components?

What is it?

With standalone Angular, we don't need to create an NgModule simply to organize and lazy load components.

There are two important approaches:

Single Component
      ↓
loadComponent()


Multiple Feature Routes
      ↓
loadChildren()

Approach 1 — loadComponent()

Use loadComponent() when you want to lazy load an individual standalone component.

Full Simple Example

products.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-products',
  standalone: true,
  template: `
    <h2>Products</h2>

    <ul>
      <li>Laptop</li>
      <li>Mobile</li>
      <li>Keyboard</li>
    </ul>
  `
})
export class Products {
}

app.routes.ts

import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: 'products',
    loadComponent: () =>
      import('./products')
        .then(m => m.Products)
  }
];

app.config.ts

import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes)
  ]
};

When the user visits:

/products

Angular executes:

import('./products')

and loads the Products component.

Flow:

/products
    ↓
Angular Router
    ↓
loadComponent()
    ↓
Dynamic import
    ↓
Products
    ↓
<router-outlet>

Approach 2 — loadChildren() with Standalone Routes

If a feature contains multiple pages:

/products
/products/add
/products/10

we can place those routes in a separate route file.

Full Simple Example

product-list.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-product-list',
  standalone: true,
  template: `
    <h2>Product List</h2>
  `
})
export class ProductList {
}

add-product.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-add-product',
  standalone: true,
  template: `
    <h2>Add Product</h2>
  `
})
export class AddProduct {
}

product-details.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-product-details',
  standalone: true,
  template: `
    <h2>Product Details</h2>
  `
})
export class ProductDetails {
}

products.routes.ts

import { Routes } from '@angular/router';

import { ProductList } from './product-list';
import { AddProduct } from './add-product';
import { ProductDetails } from './product-details';

export const PRODUCT_ROUTES: Routes = [
  {
    path: '',
    component: ProductList
  },
  {
    path: 'add',
    component: AddProduct
  },
  {
    path: ':id',
    component: ProductDetails
  }
];

app.routes.ts

import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: 'products',
    loadChildren: () =>
      import('./products/products.routes')
        .then(m => m.PRODUCT_ROUTES)
  }
];

Now:

/products
     ↓
ProductList


/products/add
     ↓
AddProduct


/products/10
     ↓
ProductDetails

The Products route configuration itself is loaded lazily.


NgModule vs Standalone Lazy Loading

NgModule Approach Standalone Approach
Lazy load feature module Lazy load component or routes
loadChildren() loadComponent() / loadChildren()
ProductsModule required No feature NgModule required
ProductsRoutingModule common products.routes.ts common
RouterModule.forChild() Plain Routes array
RouterModule.forRoot() provideRouter()

Side-by-Side Code

Older NgModule

{
  path: 'products',
  loadChildren: () =>
    import('./products/products.module')
      .then(m => m.ProductsModule)
}

This loads:

ProductsModule
      ↓
ProductsRoutingModule
      ↓
RouterModule.forChild()
      ↓
Components

Modern Standalone — Single Component

{
  path: 'products',
  loadComponent: () =>
    import('./products')
      .then(m => m.Products)
}

This loads:

Products Component

Modern Standalone — Feature Routes

{
  path: 'products',
  loadChildren: () =>
    import('./products/products.routes')
      .then(m => m.PRODUCT_ROUTES)
}

This loads:

PRODUCT_ROUTES
     ↓
├── ProductList
├── AddProduct
└── ProductDetails

One Important Improvement

In the previous standalone loadChildren() example, products.routes.ts statically imports its components:

import { ProductList } from './product-list';
import { AddProduct } from './add-product';

So once PRODUCT_ROUTES is loaded, those component dependencies belong to that lazy feature chunk/dependency graph.

You can go further and lazy load the individual components too:

import { Routes } from '@angular/router';

export const PRODUCT_ROUTES: Routes = [
  {
    path: '',
    loadComponent: () =>
      import('./product-list')
        .then(m => m.ProductList)
  },
  {
    path: 'add',
    loadComponent: () =>
      import('./add-product')
        .then(m => m.AddProduct)
  },
  {
    path: ':id',
    loadComponent: () =>
      import('./product-details')
        .then(m => m.ProductDetails)
  }
];

Conceptually:

Application starts
      ↓
Products routes not loaded

User visits /products
      ↓
PRODUCT_ROUTES loaded
      ↓
ProductList loaded

User later visits /products/add
      ↓
AddProduct loaded

This gives you more granular lazy loading.


Key Points

  • In traditional NgModule applications, lazy loading commonly used:
loadChildren()

to load a feature module.

  • Example:
loadChildren: () =>
  import('./products/products.module')
    .then(m => m.ProductsModule)
  • The lazy feature commonly had:
ProductsModule
ProductsRoutingModule
RouterModule.forChild()
  • With standalone Angular, an individual component can be lazy loaded using:
loadComponent()
  • Example:
loadComponent: () =>
  import('./products')
    .then(m => m.Products)
  • Multiple feature routes can be lazy loaded using:
loadChildren()

with a Routes array.

  • Standalone architecture removes the requirement to create an NgModule merely for routing/lazy-loading organization.

Interview Questions and Answers

1. How was lazy loading implemented with NgModules?

Feature modules were commonly lazy loaded using loadChildren():

{
  path: 'products',
  loadChildren: () =>
    import('./products/products.module')
      .then(m => m.ProductsModule)
}

The feature module typically contained a routing module using RouterModule.forChild().


2. How is a standalone component lazy loaded?

Using loadComponent():

{
  path: 'products',
  loadComponent: () =>
    import('./products')
      .then(m => m.Products)
}

3. Can loadChildren() be used with standalone Angular?

Yes. It can lazy load a route configuration:

{
  path: 'products',
  loadChildren: () =>
    import('./products/products.routes')
      .then(m => m.PRODUCT_ROUTES)
}

No feature NgModule is required.


4. What is the major difference?

NgModule approach
      ↓
loadChildren()
      ↓
ProductsModule


Standalone approach
      ↓
loadComponent()
      ↓
Standalone Component

OR

loadChildren()
      ↓
Standalone Routes

5. Which should you use for a single standalone page?

Usually:

loadComponent()

Example:

{
  path: 'about',
  loadComponent: () =>
    import('./about')
      .then(m => m.About)
}

6. Which should you use for a feature containing multiple routes?

A separate route configuration with loadChildren() is often a clean choice:

{
  path: 'products',
  loadChildren: () =>
    import('./products/products.routes')
      .then(m => m.PRODUCT_ROUTES)
}

Interview-ready answer

In NgModule-based Angular, lazy loading was commonly implemented with loadChildren() to dynamically load a feature module, whose routing was usually configured with RouterModule.forChild(). In standalone Angular, we can lazy load an individual standalone component using loadComponent(), or lazy load an entire feature's route configuration using loadChildren(). This removes the need to create feature NgModules solely for routing and lazy loading.

← Back to Articles