Skip to content

Angular Modules and Module System

What are Angular Modules?

Angular modules (NgModules) are a way to organize an application into cohesive blocks of functionality. They provide a compilation context for components, directives, and pipes, and make features available to other modules through exports.

Note: With Angular 17+, standalone components are the recommended approach for new applications. However, understanding modules is still important for:

  • Maintaining existing applications
  • Working with third-party libraries
  • Understanding Angular's architecture
  • Feature organization at scale

Module Fundamentals

Basic Module Structure

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

import { AppComponent } from './app.component';
import { HeaderComponent } from './components/header.component';
import { UserService } from './services/user.service';

@NgModule({
  declarations: [
    AppComponent,
    HeaderComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [
    UserService
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

NgModule Decorator Properties

  • declarations: Components, directives, and pipes that belong to this module
  • imports: Other modules whose exported features are needed
  • exports: Features that should be available to other modules
  • providers: Services that this module provides
  • bootstrap: The main application view (root component)
  • entryComponents: Components that are loaded dynamically (mostly deprecated)

Root Module (AppModule)

The root module bootstraps and launches the application:

Traditional AppModule

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';

import { AppComponent } from './app.component';
import { appRoutes } from './app.routes';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    RouterModule.forRoot(appRoutes)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Bootstrapping with Traditional Modules

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

platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch(err => console.error(err));

Feature Modules

Feature modules organize code around specific application features:

Creating a Feature Module

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

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

@NgModule({
  declarations: [
    UserListComponent,
    UserDetailComponent,
    UserFormComponent
  ],
  imports: [
    CommonModule,        // Instead of BrowserModule in feature modules
    FormsModule,
    ReactiveFormsModule,
    RouterModule.forChild(userRoutes)
  ],
  providers: [
    UserService
  ],
  exports: [
    UserListComponent    // Make available to other modules
  ]
})
export class UserManagementModule { }

Using Feature Modules

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { UserManagementModule } from './user-management/user-management.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    UserManagementModule  // Import the feature module
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Shared Modules

Shared modules contain common components, directives, and pipes used across the application:

Creating a Shared Module

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

import { LoadingSpinnerComponent } from './components/loading-spinner.component';
import { ConfirmDialogComponent } from './components/confirm-dialog.component';
import { HighlightDirective } from './directives/highlight.directive';
import { TruncatePipe } from './pipes/truncate.pipe';

@NgModule({
  declarations: [
    LoadingSpinnerComponent,
    ConfirmDialogComponent,
    HighlightDirective,
    TruncatePipe
  ],
  imports: [
    CommonModule,
    FormsModule,
    ReactiveFormsModule
  ],
  exports: [
    // Re-export Angular modules
    CommonModule,
    FormsModule,
    ReactiveFormsModule,
    // Export custom components
    LoadingSpinnerComponent,
    ConfirmDialogComponent,
    HighlightDirective,
    TruncatePipe
  ]
})
export class SharedModule { }

Using Shared Module

// feature.module.ts
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';

import { FeatureComponent } from './feature.component';

@NgModule({
  declarations: [
    FeatureComponent
  ],
  imports: [
    SharedModule  // Gets CommonModule, FormsModule, and custom components
  ]
})
export class FeatureModule { }

Core Module

Core module provides singleton services and components used only once:

Creating a Core Module

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

import { HeaderComponent } from './components/header.component';
import { FooterComponent } from './components/footer.component';
import { AuthService } from './services/auth.service';
import { LoggingService } from './services/logging.service';
import { AuthInterceptor } from './interceptors/auth.interceptor';

@NgModule({
  declarations: [
    HeaderComponent,
    FooterComponent
  ],
  imports: [
    CommonModule
  ],
  providers: [
    AuthService,
    LoggingService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ],
  exports: [
    HeaderComponent,
    FooterComponent
  ]
})
export class CoreModule {
  constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
    if (parentModule) {
      throw new Error('CoreModule is already loaded. Import it in the AppModule only.');
    }
  }
}

Using Core Module

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    CoreModule  // Import only in AppModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Lazy Loading Modules

Lazy loading loads feature modules on demand, reducing initial bundle size:

Setting Up Lazy Loading

// app.routes.ts
import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: 'users',
    loadChildren: () => import('./user-management/user-management.module')
      .then(m => m.UserManagementModule)
  },
  {
    path: 'products',
    loadChildren: () => import('./product-catalog/product-catalog.module')
      .then(m => m.ProductCatalogModule)
  },
  {
    path: '',
    redirectTo: '/users',
    pathMatch: 'full'
  }
];

Lazy Loaded Module

// user-management/user-management.module.ts
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

import { UserListComponent } from './components/user-list.component';
import { UserDetailComponent } from './components/user-detail.component';

const routes = [
  { path: '', component: UserListComponent },
  { path: ':id', component: UserDetailComponent }
];

@NgModule({
  declarations: [
    UserListComponent,
    UserDetailComponent
  ],
  imports: [
    RouterModule.forChild(routes)
  ]
})
export class UserManagementModule { }

Module vs Standalone Components

Traditional Module Approach

// feature.module.ts
@NgModule({
  declarations: [
    FeatureComponent,
    FeatureChildComponent
  ],
  imports: [
    CommonModule,
    FormsModule
  ]
})
export class FeatureModule { }

Standalone Component Approach (Angular 17+)

// feature.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { FeatureChildComponent } from './feature-child.component';

@Component({
  selector: 'app-feature',
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    FeatureChildComponent
  ],
  template: `
    <div>
      <h2>Feature Component</h2>
      <app-feature-child></app-feature-child>
    </div>
  `
})
export class FeatureComponent { }

Migrating from Modules to Standalone

Step 1: Convert Components

// Before (module-based)
@Component({
  selector: 'app-user-card',
  templateUrl: './user-card.component.html'
})
export class UserCardComponent { }

// After (standalone)
@Component({
  selector: 'app-user-card',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './user-card.component.html'
})
export class UserCardComponent { }

Step 2: Update Routing

// Before
{
  path: 'users',
  loadChildren: () => import('./users/users.module').then(m => m.UsersModule)
}

// After
{
  path: 'users',
  loadComponent: () => import('./users/users.component').then(c => c.UsersComponent)
}

Module Best Practices

1. Module Organization

src/app/
├── core/                 # Singleton services, one-time components
│   ├── services/
│   ├── guards/
│   ├── interceptors/
│   └── core.module.ts
├── shared/              # Reusable components, directives, pipes
│   ├── components/
│   ├── directives/
│   ├── pipes/
│   └── shared.module.ts
├── features/            # Feature modules
│   ├── user-management/
│   │   ├── components/
│   │   ├── services/
│   │   └── user-management.module.ts
│   └── product-catalog/
└── app.module.ts

2. Import Rules

  • BrowserModule: Only in AppModule
  • CommonModule: In all feature modules
  • RouterModule.forRoot(): Only in AppModule
  • RouterModule.forChild(): In feature modules
  • HttpClientModule: Usually in AppModule or core module

3. Provider Scoping

// Root level (singleton)
@NgModule({
  providers: [
    { provide: ApiService, useClass: ApiService }
  ]
})
export class AppModule { }

// Feature level
@NgModule({
  providers: [
    { provide: FeatureService, useClass: FeatureService }
  ]
})
export class FeatureModule { }

// Component level
@Component({
  providers: [
    { provide: ComponentService, useClass: ComponentService }
  ]
})
export class MyComponent { }

4. Avoid Circular Dependencies

// Bad - circular dependency
// user.module.ts imports order.module.ts
// order.module.ts imports user.module.ts

// Good - shared dependency
// Both modules import shared.module.ts
// shared.module.ts contains common interfaces

Module Configuration

Environment-Based Configuration

// app.module.ts
import { NgModule } from '@angular/core';
import { environment } from '../environments/environment';

@NgModule({
  imports: [
    HttpClientModule,
    environment.production ? [] : [HttpClientInMemoryWebApiModule]
  ],
  providers: [
    {
      provide: 'API_URL',
      useValue: environment.apiUrl
    }
  ]
})
export class AppModule { }

Feature Flags

@NgModule({
  providers: [
    {
      provide: 'FEATURE_FLAGS',
      useValue: {
        enableNewFeature: environment.enableNewFeature,
        enableBetaFeatures: !environment.production
      }
    }
  ]
})
export class AppModule { }

Module Testing

Testing Module Configuration

import { TestBed } from '@angular/core/testing';
import { FeatureModule } from './feature.module';
import { FeatureComponent } from './components/feature.component';

describe('FeatureModule', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [FeatureModule]
    });
  });

  it('should create feature component', () => {
    const fixture = TestBed.createComponent(FeatureComponent);
    expect(fixture.componentInstance).toBeTruthy();
  });
});

Mock Module for Testing

@NgModule({
  declarations: [
    MockFeatureComponent
  ],
  exports: [
    MockFeatureComponent
  ]
})
export class MockFeatureModule { }

When to Use Modules vs Standalone

Use Modules When

  • Working with existing module-based applications
  • Need complex provider configuration
  • Want to group related functionality
  • Working with libraries that require modules

Use Standalone When

  • Starting new applications (Angular 17+)
  • Want simpler component management
  • Prefer explicit imports
  • Building libraries

Migration Strategies

Gradual Migration

  1. Start with new components: Create new components as standalone
  2. Convert leaf components: Convert components with no dependencies first
  3. Update routing: Migrate to component-based lazy loading
  4. Remove empty modules: Clean up modules that no longer have declarations

Hybrid Approach

// Can mix modules and standalone components
@NgModule({
  imports: [
    CommonModule,
    StandaloneComponent  // Import standalone component in module
  ]
})
export class HybridModule { }

Summary

Angular modules provide:

  • Organization: Group related functionality together
  • Encapsulation: Control what's visible to other parts of the app
  • Lazy Loading: Load features on demand
  • Provider Scoping: Control service instantiation
  • Compilation Context: Define compilation boundaries

Key concepts:

  • Root module bootstraps the application
  • Feature modules organize functionality
  • Shared modules provide common features
  • Core modules contain singleton services
  • Lazy loading improves performance
  • Standalone components are the future

Next Steps

Now that you understand modules, you're ready to explore:

  1. Dependency Injection in detail
  2. Services and their scoping
  3. Advanced routing patterns
  4. Component communication strategies
  5. Performance optimization techniques

Understanding modules helps you architect scalable Angular applications, whether using the traditional module system or migrating to standalone components!