In Angular, as applications grow in size and complexity, managing services that need to be shared across the entire application becomes crucial. Angular provides a design pattern called the Core Module to handle this scenario. The Core Module is a dedicated module where singleton services, application-wide guards, interceptors, and other global providers are declared.
This post explores the purpose of CoreModule, how to use it to provide singleton services, and practical examples like AuthService and LoggerService, along with best practices and potential pitfalls.
Table of Contents
- Introduction to Core Module
- Purpose of CoreModule
- Singleton Services
- Global Application Utilities
- Guards and Interceptors
- Creating a CoreModule
- Basic Structure
- Importing CoreModule in AppModule
- Providing Singleton Services Across the Application
- AuthService
- LoggerService
- Ensuring CoreModule is Imported Only Once
- Example Application Using CoreModule
- Authentication Service Example
- Logging Service Example
- Best Practices
- Common Pitfalls
- Advanced Techniques
- Conclusion
1. Introduction to Core Module
Angular encourages modularity through NgModules, which organize related components, directives, pipes, and services into logical units.
- Root Module (AppModule): Bootstraps the application.
- Feature Modules: Encapsulate specific functionality like
UserModule
orProductModule
. - SharedModule: Contains reusable components, directives, and pipes.
- CoreModule: Provides singleton services and global application utilities.
The CoreModule pattern ensures that services are instantiated only once and shared across the entire application.
2. Purpose of CoreModule
The CoreModule serves multiple purposes in Angular applications:
2.1 Singleton Services
- Services that need only one instance across the application should be provided via CoreModule.
- Examples include authentication services, logging services, configuration services, and error handling services.
2.2 Global Application Utilities
- Services or utilities that support the entire application like theme management, language translation, or global event broadcasting.
2.3 Guards and Interceptors
- Route guards (
CanActivate
,CanLoad
) can be provided in CoreModule. - HTTP interceptors that need to apply globally to all requests should be registered in CoreModule.
3. Creating a CoreModule
3.1 Basic Structure
Create a dedicated module named core.module.ts:
import { NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [CommonModule],
providers: [] // singleton services go here
})
export class CoreModule {
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error('CoreModule is already loaded. Import only in AppModule.');
}
}
}
@Optional()
and@SkipSelf()
ensure that CoreModule is imported only once, preventing multiple instances of singleton services.
3.2 Importing CoreModule in AppModule
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 // CoreModule imported only in AppModule
],
bootstrap: [AppComponent]
})
export class AppModule {}
- CoreModule should never be imported in feature modules.
4. Providing Singleton Services Across the Application
Services in Angular are singleton by default if provided in the root injector or a module imported only once. CoreModule ensures application-wide singleton scope.
4.1 AuthService
import { Injectable } from '@angular/core';
@Injectable()
export class AuthService {
private isAuthenticated = false;
login(username: string, password: string): boolean {
if (username === 'admin' && password === '1234') {
this.isAuthenticated = true;
console.log('Login successful');
return true;
}
console.log('Login failed');
return false;
}
logout() {
this.isAuthenticated = false;
console.log('Logged out');
}
isLoggedIn(): boolean {
return this.isAuthenticated;
}
}
- AuthService can now be injected anywhere in the application and maintain its state across components.
4.2 LoggerService
import { Injectable } from '@angular/core';
@Injectable()
export class LoggerService {
log(message: string) {
console.log([LOG]: ${message}
);
}
error(message: string) {
console.error([ERROR]: ${message}
);
}
warn(message: string) {
console.warn([WARN]: ${message}
);
}
}
- Provides centralized logging for debugging and monitoring.
4.3 Registering Services in CoreModule
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 {
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error('CoreModule is already loaded. Import only in AppModule.');
}
}
}
- AuthService and LoggerService are singleton services for the entire application.
5. Ensuring CoreModule is Imported Only Once
Using the constructor check with @Optional()
and @SkipSelf()
prevents accidental multiple imports:
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error('CoreModule is already loaded. Import only in AppModule.');
}
}
- This ensures singleton behavior of services.
- Prevents duplicate instances of AuthService or LoggerService.
6. Example Application Using CoreModule
6.1 Authentication Service Example
Login Component:
import { Component } from '@angular/core';
import { AuthService } from '../core/auth.service';
@Component({
selector: 'app-login',
template: `
<input type="text" [(ngModel)]="username" placeholder="Username">
<input type="password" [(ngModel)]="password" placeholder="Password">
<button (click)="login()">Login</button>
<p *ngIf="!authService.isLoggedIn()">You are not logged in</p>
<p *ngIf="authService.isLoggedIn()">Welcome, {{ username }}</p>
`
})
export class LoginComponent {
username = '';
password = '';
constructor(public authService: AuthService) {}
login() {
this.authService.login(this.username, this.password);
}
}
- AuthService is singleton, so login state is maintained across components.
6.2 Logger Service Example
Home Component:
import { Component, OnInit } from '@angular/core';
import { LoggerService } from '../core/logger.service';
@Component({
selector: 'app-home',
template: <p>Check console for logs</p>
})
export class HomeComponent implements OnInit {
constructor(private logger: LoggerService) {}
ngOnInit() {
this.logger.log('Home component initialized');
this.logger.warn('This is a warning message');
}
}
- LoggerService provides centralized logging for debugging.
7. Best Practices
- CoreModule should only be imported in AppModule.
- Use CoreModule to provide singleton services.
- Avoid declaring components in CoreModule unless they are application-wide layout components (e.g., header, footer).
- Register HTTP interceptors and guards in CoreModule.
- Combine CoreModule and SharedModule: CoreModule for singleton services, SharedModule for reusable UI components.
8. Common Pitfalls
- Importing CoreModule in multiple feature modules causing duplicate service instances.
- Declaring feature-specific components in CoreModule.
- Using
providedIn: 'root'
inconsistently with CoreModule services. - Forgetting to add singleton services to CoreModule providers.
9. Advanced Techniques
- Lazy-loading and CoreModule: Do not import CoreModule in lazy-loaded feature modules.
- Singleton guards and interceptors: Provide them in CoreModule to apply globally.
Example: HTTP Interceptor:
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const cloned = req.clone({ setHeaders: { Authorization: 'Bearer token' } });
return next.handle(cloned);
}
}
Register in CoreModule:
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]
- AuthInterceptor applies globally and is singleton.
Leave a Reply