Angular Modules
Introduction
Angular modules, or NgModules, are a fundamental building block in Angular applications. They provide a way to organize your application into cohesive blocks of related functionality. If you're coming from other programming paradigms, you can think of modules as containers that group related code together.
Every Angular application has at least one module, called the root module (typically named AppModule
). As your application grows, you'll likely create additional feature modules to organize your code better and potentially enable features like lazy loading for improved performance.
In this guide, we'll explore what Angular modules are, how they work, and how to effectively use them in your applications.
What is an Angular Module?
An Angular module is a class decorated with the @NgModule
decorator. This decorator takes a metadata object that describes how the module should be compiled and instantiated.
Here's what a basic module looks like:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
imports: [BrowserModule],
declarations: [AppComponent],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
The @NgModule
decorator takes a metadata object with the following properties:
- declarations: Components, directives, and pipes that belong to this module
- imports: Other modules whose exported components, directives, or pipes are needed by components in this module
- exports: Components, directives, and pipes that can be used in templates of components in other modules that import this module
- providers: Services that the module contributes to the global collection of services
- bootstrap: The main application view, called the root component, which hosts all other app views
The Root Module
Every Angular application has a root module, conventionally named AppModule
. This module bootstraps the application and is the entry point for Angular to start the application.
Let's examine a typical root module:
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent,
HomeComponent,
AboutComponent
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
This root module:
- Imports the
BrowserModule
(essential for browser-based apps) andFormsModule
(for form features) - Declares the app component and other components it needs
- Specifies that
AppComponent
should be bootstrapped
The root module is then referenced in the main.ts
file which bootstraps the application:
// 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
As your application grows, you'll want to organize it into feature modules. Feature modules help you organize related code into discrete units, making your application easier to understand and maintain.
Here's an example of a feature module for a product-related functionality:
// product/product.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ProductListComponent } from './product-list/product-list.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';
import { ProductService } from './product.service';
@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
ProductListComponent,
ProductDetailComponent
],
providers: [ProductService],
exports: [ProductListComponent]
})
export class ProductModule { }
Note that we import CommonModule
instead of BrowserModule
in feature modules. BrowserModule
should only be imported in the root module as it contains services that are essential for application startup.
To use this feature module, we need to import it into our root module:
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { ProductModule } from './product/product.module';
@NgModule({
imports: [
BrowserModule,
ProductModule
],
declarations: [AppComponent],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Shared Modules
Shared modules are feature modules that make components, directives, and pipes available to other modules. They typically don't have their own services but rather focus on reusable UI components.
Here's an example of a shared module:
// shared/shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HighlightDirective } from './highlight.directive';
import { ButtonComponent } from './button/button.component';
import { CardComponent } from './card/card.component';
@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
HighlightDirective,
ButtonComponent,
CardComponent
],
exports: [
CommonModule,
FormsModule,
HighlightDirective,
ButtonComponent,
CardComponent
]
})
export class SharedModule { }
Notice that:
- The shared module both declares and exports the components and directives
- It also re-exports
CommonModule
andFormsModule
so any module that importsSharedModule
automatically has access to these common Angular modules
Lazy Loading Feature Modules
One of the biggest benefits of using feature modules is the ability to load them lazily. Lazy loading means that parts of your application load only when needed, which can significantly improve startup performance.
To create a lazy-loaded module, you need to set up the routing configuration:
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Then, the admin module would have its own routing module:
// admin/admin-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AdminDashboardComponent } from './admin-dashboard/admin-dashboard.component';
import { AdminUsersComponent } from './admin-users/admin-users.component';
const routes: Routes = [
{
path: '',
component: AdminDashboardComponent
},
{
path: 'users',
component: AdminUsersComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AdminRoutingModule { }
And the admin module itself:
// admin/admin.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AdminRoutingModule } from './admin-routing.module';
import { AdminDashboardComponent } from './admin-dashboard/admin-dashboard.component';
import { AdminUsersComponent } from './admin-users/admin-users.component';
@NgModule({
imports: [
CommonModule,
AdminRoutingModule
],
declarations: [
AdminDashboardComponent,
AdminUsersComponent
]
})
export class AdminModule { }
Now the admin module will only be loaded when a user navigates to '/admin'.
Core Module
A core module is typically used for singleton services that you want to load once at application startup. It's a good place for services that are used throughout your application.
// core/core.module.ts
import { NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AuthService } from './auth.service';
import { LoggerService } from './logger.service';
@NgModule({
imports: [CommonModule],
providers: [
AuthService,
LoggerService
]
})
export class CoreModule {
// Prevent core module from being imported multiple times
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error(
'CoreModule is already loaded. Import it in the AppModule only');
}
}
}
The constructor in this module ensures that it can only be imported once, which is important for singleton services.
Real-World Example: Building a Blog Application
Let's walk through a practical example of organizing a blog application using Angular modules.
App Module Structure
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';
import { SharedModule } from './shared/shared.module';
import { HomeComponent } from './home/home.component';
@NgModule({
declarations: [
AppComponent,
HomeComponent
],
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule,
CoreModule,
SharedModule
],
bootstrap: [AppComponent]
})
export class AppModule { }
Core Module
// core/core.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthService } from './services/auth.service';
import { BlogService } from './services/blog.service';
import { AuthInterceptor } from './interceptors/auth.interceptor';
import { HeaderComponent } from './components/header/header.component';
import { FooterComponent } from './components/footer/footer.component';
@NgModule({
declarations: [
HeaderComponent,
FooterComponent
],
imports: [CommonModule],
exports: [
HeaderComponent,
FooterComponent
],
providers: [
AuthService,
BlogService,
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]
})
export class CoreModule { }
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/loading-spinner.component';
import { MarkdownPipe } from './pipes/markdown.pipe';
import { HighlightDirective } from './directives/highlight.directive';
@NgModule({
declarations: [
LoadingSpinnerComponent,
MarkdownPipe,
HighlightDirective
],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule
],
exports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
LoadingSpinnerComponent,
MarkdownPipe,
HighlightDirective
]
})
export class SharedModule { }
Feature Module: Posts
// posts/posts.module.ts
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { SharedModule } from '../shared/shared.module';
import { PostListComponent } from './components/post-list/post-list.component';
import { PostDetailComponent } from './components/post-detail/post-detail.component';
import { PostEditorComponent } from './components/post-editor/post-editor.component';
import { CommentSectionComponent } from './components/comment-section/comment-section.component';
@NgModule({
declarations: [
PostListComponent,
PostDetailComponent,
PostEditorComponent,
CommentSectionComponent
],
imports: [
SharedModule,
RouterModule.forChild([
{ path: '', component: PostListComponent },
{ path: 'new', component: PostEditorComponent },
{ path: ':id', component: PostDetailComponent },
{ path: ':id/edit', component: PostEditorComponent }
])
]
})
export class PostsModule { }
App Routing with Lazy Loading
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AuthGuard } from './core/guards/auth.guard';
const routes: Routes = [
{ path: '', component: HomeComponent },
{
path: 'posts',
loadChildren: () => import('./posts/posts.module').then(m => m.PostsModule)
},
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
canLoad: [AuthGuard]
},
{
path: 'auth',
loadChildren: () => import('./auth/auth.module').then(m => m.AuthModule)
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
This example demonstrates a well-organized Angular application with:
- A root module that brings everything together
- A core module for singleton services and app-wide components like header and footer
- A shared module for common utilities, components, and directives
- Feature modules organized by domain functionality (posts, admin, auth)
- Lazy loading for features that aren't needed on application startup
Summary
Angular modules are a powerful way to organize your application. They help you:
- Organize related functionality into cohesive units
- Manage dependencies effectively
- Enable lazy loading for better performance
- Clearly separate concerns in your application
- Make your code more maintainable and testable
Key takeaways:
- Every Angular application has a root module (usually
AppModule
) - Import
BrowserModule
in the root module only, and useCommonModule
in feature modules - Use feature modules to organize related functionality
- Create shared modules for reusable components, directives, and pipes
- Implement lazy loading for better performance
- Use the core module for singleton services
Additional Resources
- Official Angular Documentation on NgModules
- Angular Style Guide: NgModule Guidelines
- Lazy Loading Feature Modules
Exercises
- Create a simple Angular application with a root module and at least two feature modules
- Implement a shared module with a custom directive and a reusable component
- Set up lazy loading for one of your feature modules
- Implement a core module with a singleton service that is used throughout your application
- Create a feature module that depends on components from your shared module
By mastering Angular modules, you'll be able to create well-structured, maintainable, and performant Angular applications.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)