← Back to Tutorials Chapter 10

Modules

What are NgModules?

An NgModule is a container that organizes related components, directives, pipes, and services into cohesive blocks of functionality. Every Angular application has at least one module — the root module. As applications grow, you split them into feature modules, shared modules, and routing modules to maintain clarity and enable lazy loading.

Root Module (AppModule)

The root module is the entry point of the application. It bootstraps the root component and imports essential modules like BrowserModule and HttpClientModule.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  declarations: [AppComponent],          // Components, directives, pipes belonging to this module
  imports: [BrowserModule, HttpClientModule, AppRoutingModule],  // Other modules this module needs
  providers: [],                          // Services available application-wide
  bootstrap: [AppComponent]              // Root component to bootstrap
})
export class AppModule { }

NgModule Metadata Properties

Feature Modules

Feature modules group related functionality for a specific feature area (e.g., Products, Orders, Admin). They help organize code into logical boundaries and enable lazy loading.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ProductListComponent } from './product-list.component';
import { ProductDetailComponent } from './product-detail.component';
import { ProductsRoutingModule } from './products-routing.module';

@NgModule({
  declarations: [ProductListComponent, ProductDetailComponent],
  imports: [CommonModule, FormsModule, ProductsRoutingModule],
  exports: [ProductListComponent]  // Optional: expose if used outside this module
})
export class ProductsModule { }
Feature modules should import CommonModule instead of BrowserModule. BrowserModule should only be imported once — in the root module. CommonModule provides common directives like ngIf and ngFor.

Shared Modules

Shared modules contain commonly used components, directives, pipes, and pipes that multiple feature modules need. Create a shared module to declare and export reusable pieces.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HighlightDirective } from './highlight.directive';
import { TruncatePipe } from './truncate.pipe';
import { LoadingSpinnerComponent } from './loading-spinner.component';

@NgModule({
  declarations: [HighlightDirective, TruncatePipe, LoadingSpinnerComponent],
  imports: [CommonModule, FormsModule],
  exports: [
    HighlightDirective,       // Custom directive
    TruncatePipe,             // Custom pipe
    LoadingSpinnerComponent,  // Custom component
    CommonModule,             // Re-export CommonModule so importing modules get ngIf/ngFor
    FormsModule               // Re-export FormsModule for template-driven forms
  ]
})
export class SharedModule { }

Feature modules then import SharedModule to access all the common pieces without duplicating declarations.

Routing Modules

Routing modules separate routing configuration from the feature module itself. This follows Angular's recommended pattern and enables lazy loading easily.

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductListComponent } from './product-list.component';
import { ProductDetailComponent } from './product-detail.component';
import { AuthGuard } from '../core/auth.guard';

const routes: Routes = [
  { path: '', component: ProductListComponent },
  { path: ':id', component: ProductDetailComponent, canActivate: [AuthGuard] }
];

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

Module Organization Best Practices

As your application scales, organize modules into layers:

Avoid re-importing services in lazy-loaded feature modules through the module's providers array. Doing so creates a separate instance of the service that is scoped to that module, which can lead to unexpected behavior if a singleton was intended. Use providedIn: 'root' on services instead.
Angular module architecture

Example: AppModule with All Module Types

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';
import { SharedModule } from './shared/shared.module';
import { ProductsModule } from './products/products.module';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    CoreModule,         // Singleton services and app shell
    SharedModule,       // Common components, pipes, directives
    ProductsModule,     // Feature module (can be lazy-loaded in production)
    AppRoutingModule    // Root routing — should be last
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Summary

You now understand Angular module architecture: the root module (AppModule), feature modules for organizing feature areas, shared modules for reusable declarations, and routing modules for route configuration. You also learned about NgModule metadata properties (declarations, imports, exports, providers, bootstrap) and best practices for structuring enterprise applications.

Exercise 10: Create a SharedModule that exports a HighlightDirective, a SortByPipe, and a ModalComponent. Create a CoreModule that provides an AuthService (singleton), an auth interceptor, and a HeaderComponent. Create a feature OrdersModule with its own routing module. Then refactor the AppModule to import CoreModule, SharedModule, and lazy-load OrdersModule.