Angular Modules & Lazy Loading
What are Angular Modules?
An Angular module is a container that groups related components, directives, pipes, and services into a cohesive block of functionality. Every Angular application has at least one module — the root module (AppModule). As an application grows, features can be organized into separate feature modules to keep the codebase maintainable and enable performance optimization through lazy loading.
Think of modules as departments in a company. The root module is the company headquarters — it manages the overall structure. Each feature module is a department (Sales, HR, Support) that has its own staff (components), tools (services), and resources (pipes, directives). Departments only share what they explicitly choose to export.
The Structure of a Module
Every Angular module is defined using the @NgModule decorator with four main configuration arrays:
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { SharedModule } from './shared/shared.module';
@NgModule({
declarations: [
AppComponent // Components, directives, and pipes that BELONG to this module
],
imports: [
BrowserModule, // Angular's core browser features
AppRoutingModule, // Routing configuration
SharedModule // Shared reusable module
],
exports: [], // What this module makes available to OTHER modules that import it
providers: [], // Services with module-level scope
bootstrap: [AppComponent] // Root component (only in AppModule)
})
export class AppModule { }
Types of Angular Modules
Root Module (AppModule)
The root module is the entry point of the application. There is exactly one root module. It bootstraps the application by loading the root component.
Feature Modules
Feature modules group the code for a specific feature — such as user management, products, or orders. They keep related code together and can be loaded lazily for better performance.
Shared Module
A shared module contains components, directives, and pipes that are used in multiple feature modules. By placing them in a shared module, each feature module imports the shared module once instead of re-declaring the same components multiple times.
Core Module
A core module contains singleton services and components that are used globally — such as the navigation bar, footer, and authentication service. The core module is imported only once in AppModule.
Creating a Feature Module
ng generate module features/products --routing
This creates:
src/app/features/products/
├── products.module.ts
└── products-routing.module.ts
// products.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductsRoutingModule } from './products-routing.module';
import { ProductListComponent } from './product-list/product-list.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';
import { ProductCardComponent } from './product-card/product-card.component';
import { SharedModule } from '../../shared/shared.module';
@NgModule({
declarations: [
ProductListComponent,
ProductDetailComponent,
ProductCardComponent
],
imports: [
CommonModule, // Provides *ngIf, *ngFor, and other common directives
ProductsRoutingModule, // Feature-specific routes
SharedModule // Shared components
]
})
export class ProductsModule { }
Note: Feature modules use CommonModule instead of BrowserModule. BrowserModule is only imported in the root AppModule. CommonModule provides all the common directives and pipes that feature modules need.
Creating a Shared Module
// shared/shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoadingSpinnerComponent } from './loading-spinner/loading-spinner.component';
import { AlertComponent } from './alert/alert.component';
import { TruncatePipe } from './pipes/truncate.pipe';
import { FormsModule } from '@angular/forms';
@NgModule({
declarations: [
LoadingSpinnerComponent,
AlertComponent,
TruncatePipe
],
imports: [
CommonModule,
FormsModule
],
exports: [
// Export everything that other modules need
LoadingSpinnerComponent,
AlertComponent,
TruncatePipe,
CommonModule, // Re-export CommonModule so importing modules get it too
FormsModule // Re-export FormsModule for convenience
]
})
export class SharedModule { }
What is Lazy Loading?
By default, Angular loads all modules when the application first starts. For large applications with many features, this means the browser downloads a large JavaScript bundle upfront — even for pages the user may never visit.
Lazy loading is a technique where feature modules are only downloaded when the user navigates to that feature's route. This significantly reduces the initial bundle size and speeds up the application's initial load time.
Analogy: Instead of stocking a warehouse with every product upfront, lazy loading is like ordering stock only when a customer places an order for it.
Setting Up Lazy Loading
Lazy loading is configured in the routing module using a dynamic import instead of a direct component reference:
// 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 }, // Eagerly loaded (loads with the app)
{
path: 'products',
loadChildren: () =>
import('./features/products/products.module').then(m => m.ProductsModule)
// ↑ Loaded ONLY when the user navigates to /products
},
{
path: 'orders',
loadChildren: () =>
import('./features/orders/orders.module').then(m => m.OrdersModule)
// ↑ Loaded ONLY when the user navigates to /orders
},
{
path: 'admin',
loadChildren: () =>
import('./features/admin/admin.module').then(m => m.AdminModule)
// ↑ Loaded ONLY when the user navigates to /admin
}
];
Configuring Routes Within the Lazy-Loaded Module
// products-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductListComponent } from './product-list/product-list.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';
// These paths are RELATIVE to the parent path (/products)
const routes: Routes = [
{ path: '', component: ProductListComponent }, // /products
{ path: ':id', component: ProductDetailComponent } // /products/5
];
@NgModule({
imports: [RouterModule.forChild(routes)], // Use forChild — not forRoot
exports: [RouterModule]
})
export class ProductsRoutingModule { }
The key difference is RouterModule.forChild(routes) instead of RouterModule.forRoot(routes). forRoot is only used once in the root routing module. All feature routing modules use forChild.
Preloading Strategies
Angular provides preloading strategies that control when lazy-loaded modules are downloaded in the background.
No Preloading (Default)
Modules are only loaded when the user navigates to their route. Nothing downloads in the background.
PreloadAllModules
After the application loads, Angular automatically downloads all lazy-loaded modules in the background. The initial load is fast, and subsequent navigation is instant because the modules are already available.
import { RouterModule, PreloadAllModules } from '@angular/router';
@NgModule({
imports: [RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules
})]
})
export class AppRoutingModule { }
Verifying Lazy Loading Works
To confirm lazy loading is working, open Chrome DevTools (F12) and go to the Network tab. Filter by "JS" files. When the app first loads, only the main bundle should download. Navigate to the /products route — a separate chunk file for the products module should appear in the Network tab at that moment.
Module Best Practices
When to Create a Feature Module
- The feature has multiple components that only interact with each other.
- The feature is large enough to benefit from lazy loading.
- The feature could theoretically be reused in another application.
What Goes in SharedModule
- Generic UI components (buttons, modals, cards) used in 3+ feature modules.
- Pipes used throughout the application.
- Directives used throughout the application.
What Goes in CoreModule
- Singleton services (authentication, logging).
- Application-wide components (header, footer, sidebar).
- HTTP interceptors.
Summary
Angular modules organize code into logical, self-contained blocks. Every application has a root module (AppModule), and larger applications use feature modules for each distinct functionality area. The shared module groups reusable components, pipes, and directives. The core module holds singleton services and global layout components. Lazy loading defers the download of feature module bundles until the user navigates to that feature, reducing initial load time. Lazy loading is configured via loadChildren with dynamic imports in the routing module. Feature module routes use RouterModule.forChild() instead of forRoot(). The PreloadAllModules strategy balances fast initial load with quick subsequent navigation.
