AngularJSNgModules

NgModules

NgModules are Angular's original mechanism for organizing an application into cohesive blocks of functionality. An NgModule is a class decorated with @NgModule that declares a compilation context for components, directives, and pipes — and configures the dependency injection system.

While Angular 14+ introduced standalone components that can work without NgModules, NgModules remain common in existing codebases and are important to understand.

What Is an NgModule?

An NgModule is a container that groups:

  • Declarations — components, directives, and pipes that belong to this module
  • Imports — other modules whose exported declarations this module needs
  • Exports — what this module makes available to other modules
  • Providers — services registered with this module's injector
  • Bootstrap — the root component (root module only)

TS
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HeaderComponent } from './components/header.component';
import { FooterComponent } from './components/footer.component';

@NgModule({
  declarations: [
    AppComponent,      // root component
    HeaderComponent,   // components declared in this module
    FooterComponent,
  ],
  imports: [
    BrowserModule,        // core browser functionality
    CommonModule,         // NgIf, NgFor, pipes, etc.
    ReactiveFormsModule,  // reactive forms support
    HttpClientModule,     // HTTP client
    AppRoutingModule,     // routing configuration
  ],
  providers: [
    // services registered here are available app-wide
    // (unless already using providedIn: 'root')
  ],
  bootstrap: [AppComponent], // root module only
})
export class AppModule {}
The Root Module (AppModule)

Every module-based Angular app has exactly one root module, conventionally called AppModule. It is the entry point that Angular uses to bootstrap the application.

The root module is bootstrapped in main.ts:

TS
// main.ts (module-based app)
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch(err => console.error(err));
Note
Modern Angular apps (Angular 14+) use bootstrapApplication() with standalone components instead of bootstrapModule(AppModule). Both approaches are valid and widely used.
Feature Modules

As apps grow, you organize functionality into feature modules. Each feature module encapsulates everything related to one area of the app:

TS
// user/user.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { UserRoutingModule } from './user-routing.module';

import { UserListComponent } from './user-list/user-list.component';
import { UserDetailComponent } from './user-detail/user-detail.component';
import { UserFormComponent } from './user-form/user-form.component';
import { UserService } from './user.service';

@NgModule({
  declarations: [
    UserListComponent,
    UserDetailComponent,
    UserFormComponent,
  ],
  imports: [
    CommonModule,
    ReactiveFormsModule,
    UserRoutingModule,
  ],
  providers: [UserService],
  exports: [
    // Only export if other modules need these components
    UserListComponent,
  ],
})
export class UserModule {}
Shared Modules

A shared module bundles commonly needed declarations and re-exports them so feature modules import one thing instead of many:

TS
// shared/shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';

// Custom shared components/pipes/directives
import { LoadingSpinnerComponent } from './loading-spinner/loading-spinner.component';
import { TruncatePipe } from './pipes/truncate.pipe';
import { HighlightDirective } from './directives/highlight.directive';
import { ButtonComponent } from './ui/button/button.component';

@NgModule({
  declarations: [
    LoadingSpinnerComponent,
    TruncatePipe,
    HighlightDirective,
    ButtonComponent,
  ],
  imports: [
    CommonModule,
    RouterModule,
  ],
  exports: [
    // Re-export Angular modules so feature modules get them too
    CommonModule,
    ReactiveFormsModule,
    FormsModule,
    RouterModule,
    // Export custom declarations
    LoadingSpinnerComponent,
    TruncatePipe,
    HighlightDirective,
    ButtonComponent,
  ],
})
export class SharedModule {}
Tip
A shared module should have NO providers (no services). Services belong in feature modules or use providedIn: 'root'. Shared modules that provide services create unexpected singleton issues.
Lazy Loading with NgModules

Lazy loading splits your app into smaller JavaScript bundles that load only when a route is visited. This dramatically improves initial load time.

TS
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  {
    path: '',
    loadChildren: () =>
      import('./features/home/home.module').then(m => m.HomeModule),
  },
  {
    path: 'products',
    loadChildren: () =>
      import('./features/products/products.module').then(m => m.ProductsModule),
  },
  {
    path: 'admin',
    loadChildren: () =>
      import('./features/admin/admin.module').then(m => m.AdminModule),
    // Route-level providers
    // Services in AdminModule are only instantiated when this route loads
  },
];

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

TS
// features/products/products.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SharedModule } from '../../shared/shared.module';
import { ProductListComponent } from './product-list/product-list.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';

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

@NgModule({
  declarations: [ProductListComponent, ProductDetailComponent],
  imports: [
    SharedModule,
    RouterModule.forChild(routes), // forChild for feature modules
  ],
})
export class ProductsModule {}
Note
Use RouterModule.forRoot(routes) only in the root module. All feature modules use RouterModule.forChild(routes).
Core Module Pattern

A core module is a convention for services and components that should exist only once in the app (singleton concerns):

TS
// core/core.module.ts
import { NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HTTP_INTERCEPTORS } from '@angular/common/http';

import { AuthInterceptor } from './interceptors/auth.interceptor';
import { NavbarComponent } from './navbar/navbar.component';
import { NotFoundComponent } from './not-found/not-found.component';

@NgModule({
  declarations: [NavbarComponent, NotFoundComponent],
  imports: [CommonModule],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
  ],
  exports: [NavbarComponent],
})
export class CoreModule {
  // Guard: throw if CoreModule is imported more than once
  constructor(@Optional() @SkipSelf() parentModule?: CoreModule) {
    if (parentModule) {
      throw new Error('CoreModule is already loaded. Import it only in AppModule.');
    }
  }
}
NgModule declarations vs imports vs exports

Property

Contains

Makes available to

declarations

Components, directives, pipes defined in this module

Templates within this module only

imports

Other NgModules

Templates in this module (via imports exported declarations)

exports

Components, directives, pipes, or whole modules

Other modules that import this module

providers

Services, InjectionToken values

The entire app (for root) or module tree (for feature)

bootstrap

Root component (AppModule only)

N/A — this is the entry point

NgModules vs Standalone Components

Angular 14+ standalone components replace NgModules for most purposes. Here is when to choose each:

Aspect

NgModules

Standalone Components

Introduced

Angular 2

Angular 14 (stable in 15)

Boilerplate

High — module file per feature

Low — just component file

Lazy loading

loadChildren: module

loadComponent: component

Tree-shaking

Module-level

Component-level (better)

Existing codebases

Common

Migrating to standalone

Angular recommendation

Legacy / large teams

New projects (default in 17+)

TS
// Equivalent lazy loading with standalone component
const routes: Routes = [
  {
    path: 'products',
    loadComponent: () =>
      import('./features/products/product-list.component')
        .then(m => m.ProductListComponent),
  },
];
Migrating to Standalone

Angular CLI provides an automatic migration to convert NgModule-based apps to standalone:

Bash
# Migrate components one by one
ng generate @angular/core:standalone

# Choose migration mode:
# 1. Convert all components/directives/pipes to standalone
# 2. Remove unnecessary NgModules
# 3. Bootstrap with standalone API
Tip
The migration is safe to run incrementally. You can have a mix of standalone components and NgModule-declared components in the same app.
Common NgModule Errors
  • Component X is not a known element — the component's module is not imported

  • Can't bind to 'formGroup' — ReactiveFormsModule is not imported

  • Unexpected component X — component is declared in two modules; move to SharedModule and export

  • NullInjectorError — service is in a lazy module but being injected in the root; use providedIn: 'root'

  • Type ComponentX is not part of any NgModule — component declared but module not imported into AppModule

Warning
A component can only be declared in ONE NgModule. If you need it in multiple places, either declare it in a SharedModule and export/import that, or convert it to a standalone component.
Summary

NgModules are Angular's original code organization system:

  • declarations: own components, directives, and pipes
  • imports: modules providing needed functionality
  • exports: what other modules can use from this module
  • providers: services scoped to this module
  • Lazy loading: use loadChildren to split bundles by feature
  • Core module: single-instance services and app-wide components
  • Shared module: reusable UI components and pipes

While standalone components are Angular's future, NgModules remain essential knowledge for the vast majority of production Angular applications in use today.