javascriptangulartypescriptangular2-services

Injecting array of services


As far as I now, services need to be provided and injected, meaning each service has to be placed inside the constructor eg.:

constructor (private a: AService, private B: BService) {}

In my situation, I have a bunch of services (all implementing the same interface) I would like to hold in an array. How can I achieve this without a lot of redundancy (since I already have to state each twice for providing and injecting - right?)? Regular classes instead of injectable services would not work for me because each of the services might need to use other services like HTTP which again have to be injected.


Solution

  • With a simple helper array, let's call it "registry" and a dedicated array provider, we can make all registered instances available. Here a quick example:

    export class Examples extends Array<ExampleService> {}
    
    const examplesRegistry = [ExampleServiceA, ExampleServiceB];
    
    export function buildExampleProviders() {
      return [
        ...examplesRegistry,
        {
          provide: Examples,
          useFactory: (...args) => new Examples(...args),
          deps: examplesRegistry,
        },
      ];
    }
    

    As demonstrated below, we can still inject single instances (like protected exampleA: ExampleServiceA) or all of them with protected examples: Examples)


    full example + usage

    /// example.service.ts
    
    import { Injectable } from '@angular/core';
    
    @Injectable()
    export abstract class ExampleService {
      someProperty = Math.random();
      abstract name: string;
    }
    
    @Injectable()
    export class ExampleServiceA extends ExampleService {
      override name = 'a';
    }
    
    @Injectable()
    export class ExampleServiceB extends ExampleService {
      override name = 'b';
    }
    
    /// examples.ts
    
    export class Examples extends Array<ExampleService> {}
    
    const examplesRegistry = [ExampleServiceA, ExampleServiceB];
    
    export function buildExampleProviders() {
      return [
        ...examplesRegistry,
        {
          provide: Examples,
          useFactory: (...args) => new Examples(...args),
          deps: examplesRegistry,
        },
      ];
    }
    
    /// app.component.ts
    
    import { Component } from '@angular/core';
    import { ExampleServiceA } from './example.service';
    import { buildExampleProviders, Examples } from './examples';
    
    @Component({
      selector: 'my-app',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
      providers: [buildExampleProviders()],
    })
    export class AppComponent {
      constructor(
        protected examples: Examples,
        protected exampleA: ExampleServiceA
      ) {}
    }
    
    <!-- app.component.html -->
    <pre>
      <ng-container *ngFor="let ex of examples; let i = index">
      examples[{{i}}]:
        .name: {{ ex.name }}
        .someProperty: {{ ex.someProperty }}
        .constructor.name: {{ ex.constructor.name }}
      </ng-container>
    
      exampleA:
        .name: {{ exampleA.name }}
        .someProperty: {{ exampleA.someProperty }}
        .constructor.name: {{ exampleA.constructor.name }}
    </pre>
    

    You can also try it out here: https://stackblitz.com/edit/angular-ivy-3kaagr?file=src/app/examples.ts