Angular Modules and Lazy Loading: Complete Guide
Angular modules organize application code into cohesive blocks. Learn how to create feature modules, shared modules, implement lazy loading, and organize module architecture for scalable, performant Angular applications.
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.
In this guide, I'll show you how I organize modules in production Angular applications. We'll cover the root AppModule (what goes here vs feature modules), feature modules (organizing code by feature), shared modules (reusable components and directives), lazy loading implementation (reducing initial bundle size), module architecture patterns, and best practices for organizing large applications. I'll also share some gotchas I've encountered with module loading and dependency management.
Root Module (AppModule)
The main application module:
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 { }Feature Module
Organize features into 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,
ReactiveFormsModule,
BusinessRoutingModule,
SharedModule
],
providers: [BusinessService]
})
export class BusinessModule { }Lazy Loading
Implement lazy loading for feature modules:
// app-routing.module.ts
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)
}
];
// business-routing.module.ts
const businessRoutes: Routes = [
{ path: '', component: BusinessListComponent },
{ path: ':id', component: BusinessDetailsComponent },
{ path: ':id/settings', component: BusinessSettingsComponent }
];
@NgModule({
imports: [RouterModule.forChild(businessRoutes)],
exports: [RouterModule]
})
export class BusinessRoutingModule { }Shared Module
Create reusable shared components:
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: []
};
}
}Core Module
Organize singleton services:
import { NgModule, ModuleWithProviders } 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');
}
}
}Best Practices
- Use lazy loading for feature modules to 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 on a single feature or domain
- Export only what other modules need from shared modules
- Avoid importing the same module multiple times
- Use forRoot() pattern for modules with providers
- Organize modules by feature, not by file type
- Keep AppModule minimal, delegate to feature modules
- Use module boundaries to enforce architecture
Conclusion
Angular modules 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.