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.

Leave a Comment

Your email address will not be published. Required fields are marked *