3

I have a service with multiple instances, that I provide multiple.

  { provide: MyAbstractService, useClass: MyServiceA, multi: true },
  { provide: MyAbstractService, useClass: MyServiceB, multi: true },

For reasons I don't further want to name, I want to use the new inject function to inject those services instead of constructor injection.

Before Angular 14 I would have done that in constructor:

@Inject(MyAbstractService) myServices: MyAbstractService[],

I expected this to work, but it doesn't:

private myServices: MyAbstractService[] = inject(MyAbstractService);

inject seems to never return an array, hence I can't assign it to an array type. How to tell inject that I expect an array of services here? Following seems to work, but also seems dirty:

private myServices: MyAbstractService[] = inject(MyAbstractService) as unknown as MyAbstractService[];

See stackblitz example: https://stackblitz.com/edit/angular-ivy-gayhxw?file=src/app/app.component.ts

MoxxiManagarm
  • 8,735
  • 3
  • 14
  • 43

1 Answers1

1

Using a dedicated token:

  1. Create a dedicated token that represents an array of services using InjectionToken:

    import { InjectionToken } from '@angular/core';
    export const MY_SERVICES_TOKEN = new InjectionToken<MyAbstractService[]>('MyServices');
    
  2. Register the service instances in the providers array of your module or component using the custom token and the multi: true option:

    import { MyService1, MyService2 } from './my-services';
    
    @NgModule({
      providers: [
        { provide: MY_SERVICES_TOKEN, useClass: MyService1, multi: true },
        { provide: MY_SERVICES_TOKEN, useClass: MyService2, multi: true },
      ],
    })
    export class AppModule { }
    
  3. Inject the multiple instances of the service using the inject() function and the custom token. TypeScript will infer the type of the token, allowing you to access the services in the array:

      private myServices: MyAbstractService[] = inject(MY_SERVICES_TOKEN);
    

    myServices will contain an array of instances for the services registered with MY_SERVICES_TOKEN.

Using a helper

In my opinion, angular lacks of an importMultiple function, if you want to use your base service as a token, here is that helper (typed but dropping the deprecated signature of inject)

import-multiple.helper.ts

type InjectParameters<T> = [
  ProviderToken<T>,
  (
    | InjectOptions
    | (InjectOptions & {
        optional?: false;
      })
  )?
];

function assertIsArrayOfServices<T>(value: unknown): asserts value is T[] {
  if (!Array.isArray(value)) {
    throw new Error(`Expected an array but received ${typeof value}, please provide services using multi: true`);
  }
}

export function injectMultiple<T>(...arg: InjectParameters<T>): T[] {
  const services = inject<T>(...arg) as T[];
  assertIsArrayOfServices(services);
  return services;  
}

Usage:

private myServices: MyAbstractService[] = injectMultiple(MyAbstractService);
Flavien Volken
  • 19,196
  • 12
  • 100
  • 133