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¶
- Start with new components: Create new components as standalone
- Convert leaf components: Convert components with no dependencies first
- Update routing: Migrate to component-based lazy loading
- 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:
- Dependency Injection in detail
- Services and their scoping
- Advanced routing patterns
- Component communication strategies
- Performance optimization techniques
Understanding modules helps you architect scalable Angular applications, whether using the traditional module system or migrating to standalone components!