Angular Techniques: Injecting Singleton Services into Custom Classes
Tags: angular, tutorial, programming, productivity
Published: 2024-04-17 09:48:10


### 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:

1. **Define the Service Locator Class**:
   ```typescript
   import { Injector } from '@angular/core';

   export class ServiceLocator {
     static injector: Injector;
   }
   ```

2. **Initialize the Service Locator**:
   Create a function to assign the Angular `Injector` instance to your `ServiceLocator`:
   ```typescript
   export function setupServiceLocator(injector: Injector): void {
     ServiceLocator.injector = injector;
   }
   ```

3. **Register a Provider for Initialization**:
   To ensure that the `Injector` is available as soon as possible, you need to register an `APP_INITIALIZER` provider in your application module:
   ```typescript
   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:

```typescript
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](https://www.npmjs.com/package/@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:

```bash
npm install @ng-vibe/service-locator
```

Then, configure it in your application module:

```typescript
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:

```typescript
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](https://www.npmjs.com/package/@ng-vibe/service-locator).

### Stay Connected

For updates and more insights, follow me on [LinkedIn](https://www.linkedin.com/in/boris-jenicek/).

Project Repository: [https://github.com/boris-jenicek/ng-vibe](https://github.com/boris-jenicek/ng-vibe)