How Angular's Dependency Injection Falls Short for Non-Service Classes
In Angular, dependency injection is primarily designed for services and components, facilitated through decorators like @Injectable()
and @Component()
. These decorators enable Angular to manage the creation and lifecycle of these objects. However, when you step outside this conventional use—into the realm of custom classes that are neither components nor services—you encounter a limitation. Angular's DI can't directly inject dependencies into arbitrary classes. This is where the Service Locator pattern comes into play as a workaround.
Setting Up the Angular Service Locator
The Service Locator pattern is essentially a manually created service that can access Angular’s injector itself. It acts as a central registry from which other parts of your application can retrieve dependencies. Here’s how to set it up:
- Define the Service Locator Class:
import { Injector } from '@angular/core';
export class ServiceLocator {
static injector: Injector;
}
-
Initialize the Service Locator:
Create a function to assign the Angular
Injector
instance to yourServiceLocator
:
export function setupServiceLocator(injector: Injector): void {
ServiceLocator.injector = injector;
}
-
Register a Provider for Initialization:
To ensure that the
Injector
is available as soon as possible, you need to register anAPP_INITIALIZER
provider in your application module:
import { ApplicationConfig, APP_INITIALIZER, Injector } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
{
provide: APP_INITIALIZER,
useFactory: (injector: Injector) => () => setupServiceLocator(injector),
deps: [Injector],
multi: true
}
]
}
Example: Using Angular Services in a Custom Class
With the Service Locator set up, you can now inject Angular services into any class. Here’s a simple example demonstrating how to use this approach:
import { ServiceLocator } from './service-locator';
import { LoggerService } from './logger.service';
export class CustomClass {
private logger: LoggerService;
constructor() {
this.logger = ServiceLocator.injector.get(LoggerService);
this.logger.log('CustomClass initialized!');
}
}
Why Use the Service Locator Pattern?
While this approach deviates from the typical Angular DI pattern, it provides a practical solution for extending dependency injection capabilities to any class within your Angular application. It's especially useful in large applications where you need to maintain clean architecture without excessively coupling your classes to Angular's components or services structure.
Simplify Angular DI with @ng-vibe/service-locator
If you're a fan of simpler, more streamlined approaches to managing dependency injection in Angular, you might be interested in a library I developed called @ng-vibe/service-locator. This library is designed to address the limitations of Angular's dependency injection for non-service classes, providing an elegant solution that supports both accessing singleton services and creating new instances dynamically.
Features of @ng-vibe/service-locator include:
- Static Access to Services: Easily retrieve instances of any registered service from anywhere in your application.
- Dynamic Service Creation: Create new service instances on-the-fly, scoped within their own injectors.
- Simplified Service Management: Streamlines the use of Angular's injector, making it more accessible and easier to manage across your application.
Getting Started
To integrate @ng-vibe/service-locator into your Angular application, simply install the package via npm:
npm install @ng-vibe/service-locator
Then, configure it in your application module:
import { provideNgVibeServiceLocator } from '@ng-vibe/service-locator';
export const appConfig: ApplicationConfig = {
providers: [
...,
provideNgVibeServiceLocator(),
],
}
Example Usage
Here’s a quick look at how you might use @ng-vibe/service-locator to manage service instances effectively:
import { ServiceLocator } from '@ng-vibe/service-locator';
import { LoggerDummyService } from '../services/logger-dummy.service';
export class CustomClass {
constructor() {
const loggerSingleton = ServiceLocator.getInstance(LoggerDummyService);
const loggerNewInstance = ServiceLocator.createService(LoggerDummyService);
}
}
Wrap-Up
The @ng-vibe/service-locator library makes extending Angular's DI capabilities straightforward and intuitive, especially useful in large applications where maintaining clean architecture is crucial. It helps you manage dependencies without tightly coupling your classes to Angular's components or services structure. For more details and how to contribute, check out the project on npm.
Stay Connected
For updates and more insights, follow me on LinkedIn.
Project Repository: https://github.com/boris-jenicek/ng-vibe
Top comments (0)