When I first started building Angular applications, I put everything in the root AppModule. It worked fine for small apps, but as the application grew, the initial bundle size became huge, and the app took forever to load. That's when I learned about Angular modules and lazy loading, and it completely changed how I structure applications.
Angular modules are containers that organize related components, directives, pipes, and services. They help manage dependencies, organize code into logical units, and most importantly, enable lazy loading. Lazy loading means feature modules are only loaded when their routes are accessed, dramatically reducing the initial bundle size and improving startup time.
📖 Want the complete guide with more examples and advanced patterns? Check out the full article on my blog for an in-depth tutorial with additional code examples, troubleshooting tips, and real-world use cases.
What are Angular Modules?
Angular Modules provide:
- Code Organization - Group related components, services, and functionality
- Dependency Management - Control what's available to other modules
- Lazy Loading - Load modules on-demand for better performance
- Encapsulation - Keep feature code isolated and maintainable
- Reusability - Share components and services across modules
Root Module (AppModule)
The main application module that bootstraps your app:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { CoreModule } from 'src/core/core.module';
import { SharedModule } from './shared/shared.module';
import { AuthInterceptor } from './core/interceptors/auth.interceptor';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
ReactiveFormsModule,
FormsModule,
CoreModule.forRoot(),
SharedModule.forRoot()
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
Key Points:
- Only declare the root
AppComponent - Import core modules (BrowserModule, HttpClientModule)
- Import routing module
- Import shared and core modules using
forRoot() - Keep AppModule minimal
Feature Module
Organize features into separate modules:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { BusinessRoutingModule } from './business-routing.module';
import { BusinessListComponent } from './business-list/business-list.component';
import { BusinessDetailsComponent } from './business-details/business-details.component';
import { BusinessSettingsComponent } from './business-settings/business-settings.component';
import { BusinessService } from 'src/services/business.service';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [
BusinessListComponent,
BusinessDetailsComponent,
BusinessSettingsComponent
],
imports: [
CommonModule, // Use CommonModule instead of BrowserModule
ReactiveFormsModule,
BusinessRoutingModule,
SharedModule
],
providers: [BusinessService]
})
export class BusinessModule { }
Key Points:
- Use
CommonModuleinstead ofBrowserModulein feature modules - Declare all components, directives, and pipes used in the feature
- Import only what the feature needs
- Provide feature-specific services
Lazy Loading
Implement lazy loading for feature modules to reduce initial bundle size:
// app-routing.module.ts
import { Routes } from '@angular/router';
const routes: Routes = [
{
path: 'business',
loadChildren: () => import('./business/business.module')
.then(m => m.BusinessModule)
},
{
path: 'users',
loadChildren: () => import('./users/users.module')
.then(m => m.UsersModule)
},
{
path: 'sites',
loadChildren: () => import('./site/site.module')
.then(m => m.SiteModule)
},
{
path: '',
redirectTo: '/dashboard',
pathMatch: 'full'
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Feature Module Routing
Each lazy-loaded module has its own routing:
// business-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { BusinessListComponent } from './business-list/business-list.component';
import { BusinessDetailsComponent } from './business-details/business-details.component';
import { BusinessSettingsComponent } from './business-settings/business-settings.component';
const businessRoutes: Routes = [
{ path: '', component: BusinessListComponent },
{ path: ':id', component: BusinessDetailsComponent },
{ path: ':id/settings', component: BusinessSettingsComponent }
];
@NgModule({
imports: [RouterModule.forChild(businessRoutes)], // Use forChild, not forRoot
exports: [RouterModule]
})
export class BusinessRoutingModule { }
Key Points:
- Use
loadChildrenwith dynamic import syntax - Use
RouterModule.forChild()in feature modules - Routes are relative to the feature module path
- Modules load only when their routes are accessed
Shared Module
Create reusable shared components, directives, and pipes:
import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UserInfoCardComponent } from './user-info-card/user-info-card.component';
import { DocumentViewerComponent } from './document-viewer/document-viewer.component';
import { ImageViewUploadComponent } from './image-view-upload/image-view-upload.component';
@NgModule({
declarations: [
UserInfoCardComponent,
DocumentViewerComponent,
ImageViewUploadComponent
],
imports: [CommonModule],
exports: [
UserInfoCardComponent,
DocumentViewerComponent,
ImageViewUploadComponent
]
})
export class SharedModule {
static forRoot(): ModuleWithProviders<SharedModule> {
return {
ngModule: SharedModule,
providers: []
};
}
}
Key Points:
- Export components, directives, and pipes that other modules need
- Import
CommonModulefor common directives (ngIf, ngFor) - Use
forRoot()pattern if the module has providers - Import SharedModule in feature modules that need shared components
Core Module
Organize singleton services in a Core module:
import { NgModule, ModuleWithProviders, Optional, SkipSelf } from '@angular/core';
import { AuthService } from './auth/auth.service';
import { ToastService } from './controls/toast-global/toast.service';
@NgModule({
declarations: [],
imports: [],
exports: []
})
export class CoreModule {
static forRoot(): ModuleWithProviders<CoreModule> {
return {
ngModule: CoreModule,
providers: [
AuthService,
ToastService
]
};
}
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error('CoreModule is already loaded. Import it in the AppModule only');
}
}
}
Key Points:
- Use
forRoot()to provide singleton services - Prevent multiple imports with constructor guard
- Import only in AppModule
- Contains app-wide singleton services
Module Architecture Patterns
Recommended Module Structure
app/
├── core/
│ ├── core.module.ts
│ ├── services/
│ └── interceptors/
├── shared/
│ ├── shared.module.ts
│ ├── components/
│ ├── directives/
│ └── pipes/
├── features/
│ ├── business/
│ │ ├── business.module.ts
│ │ ├── business-routing.module.ts
│ │ └── components/
│ ├── users/
│ │ ├── users.module.ts
│ │ ├── users-routing.module.ts
│ │ └── components/
│ └── sites/
│ ├── site.module.ts
│ ├── site-routing.module.ts
│ └── components/
├── app.module.ts
└── app-routing.module.ts
Module Types
- AppModule - Root module, bootstraps the application
- Feature Modules - Organize features (Business, Users, Sites)
- Shared Module - Reusable components, directives, pipes
- Core Module - Singleton services, app-wide providers
- Routing Modules - Route configuration for features
Lazy Loading Benefits
Performance Benefits:
- Reduced Initial Bundle Size - Only load what's needed initially
- Faster Startup Time - Smaller initial bundle loads faster
- Better Code Splitting - Each feature module becomes a separate chunk
- Improved User Experience - Users see content faster
Example Bundle Sizes:
- Without lazy loading: 2.5 MB initial bundle
- With lazy loading: 500 KB initial bundle + feature chunks on demand
Preloading Strategy
Configure preloading for better performance:
import { PreloadAllModules } from '@angular/router';
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules // Preload all lazy modules
})
],
exports: [RouterModule]
})
export class AppRoutingModule { }
Custom Preloading Strategy
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SelectivePreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
if (route.data && route.data['preload']) {
return load();
}
return of(null);
}
}
// Use in routes
{
path: 'business',
loadChildren: () => import('./business/business.module').then(m => m.BusinessModule),
data: { preload: true }
}
Common Patterns
Module with Providers
@NgModule({
providers: [BusinessService]
})
export class BusinessModule { }
Module with Exports
@NgModule({
declarations: [SharedComponent],
exports: [SharedComponent] // Make available to other modules
})
export class SharedModule { }
Module with Imports
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
SharedModule
]
})
export class FeatureModule { }
Best Practices
- Use lazy loading for feature modules - Reduce initial bundle size
- Create shared modules - For reusable components, directives, and pipes
- Use core module - For singleton services (import only in AppModule)
- Keep feature modules focused - One feature or domain per module
- Export only what's needed - From shared modules
- Avoid importing the same module multiple times - Use shared modules
- Use forRoot() pattern - For modules with providers
- Organize by feature - Not by file type
- Keep AppModule minimal - Delegate to feature modules
- Use module boundaries - To enforce architecture
Module Organization Tips
- Feature Modules - Organize by business domain (Business, Users, Products)
- Shared Module - Common UI components used across features
- Core Module - App-wide singleton services
- Routing Modules - Separate routing configuration
Common Mistakes to Avoid
❌ Don't Import BrowserModule in Feature Modules
// ❌ Wrong
@NgModule({
imports: [BrowserModule]
})
// ✅ Correct
@NgModule({
imports: [CommonModule]
})
❌ Don't Import CoreModule Multiple Times
// ❌ Wrong - Importing in feature module
@NgModule({
imports: [CoreModule]
})
// ✅ Correct - Only in AppModule
@NgModule({
imports: [CoreModule.forRoot()]
})
❌ Don't Use forRoot() in Feature Modules
// ❌ Wrong
@NgModule({
imports: [SharedModule.forRoot()]
})
// ✅ Correct
@NgModule({
imports: [SharedModule]
})
Resources and Further Reading
- 📚 Full Angular Modules Guide - Complete tutorial with advanced examples, troubleshooting, and best practices
- Angular Routing Guide - Routing patterns with lazy loading
- Angular Services Guide - Dependency injection in modules
- Angular Guards Guide - Route guards with lazy modules
- Angular Modules Documentation - Official Angular docs
- Angular Lazy Loading Guide - Official lazy loading guide
- Angular Module Best Practices - Module FAQ and best practices
Conclusion
Angular Modules and Lazy Loading provide a powerful way to organize code and manage dependencies. With proper module architecture and lazy loading, you can build scalable, maintainable, and performant Angular applications.
Key Takeaways:
- Angular Modules - Organize code into cohesive blocks
- Feature Modules - Organize by business domain
- Shared Modules - Reusable components and directives
- Core Module - Singleton services for app-wide use
- Lazy Loading - Load modules on-demand for better performance
- forRoot() Pattern - For modules with providers
- Module Architecture - Organize by feature, not by file type
- Performance - Lazy loading reduces initial bundle size
Whether you're building a small application or a large enterprise system, Angular modules provide the foundation you need. They organize your code, manage dependencies, and enable lazy loading for optimal performance.
What's your experience with Angular Modules and Lazy Loading? Share your tips and tricks in the comments below! 🚀
💡 Looking for more details? This is a condensed version of my comprehensive guide. Read the full article on my blog for additional examples, advanced patterns, troubleshooting tips, and more in-depth explanations.
If you found this guide helpful, consider checking out my other articles on Angular development and frontend development best practices.
Top comments (0)