Make sure a service is instantiated

Ben Winding picture Ben Winding · Dec 19, 2016 · Viewed 8.9k times · Source

Background

We're building an Angular2 app, and are accumulating a lot of specific services relating to one module. All these services are loosely coupled to a Subject<Type> event system in the app.

Instantiation via the Constructor

Because these services are never directly referenced, and only subscribe to events, we just have to instantiate them somehow. Currently we just inject them into a constructor of another service that is used.

// Services not used, just to make sure they're instantiated
constructor(
  private appService1: AppService1,     
  private appService2: AppService2,
  private appService3: AppService3,
  ...
){ }

This seems like a bit of a hack, is there a better way to explicitly state services that need to be instantiated without injecting them through a constructor?

Answer

Alexander Abakumov picture Alexander Abakumov · Aug 30, 2018

Another pattern of making sure a service gets instantiated depending on your preference could be to inject it into a module constructor.

This way the service gets instantiated together with the module.

So, instead of creating a brand new service just to inject another services into it as you described in your question, you could just do something like this:

@NgModule ({
    ...
})
export class SomeModule {
    // Services are not used, just to make sure they're instantiated
    constructor(
        private appService1: AppService1,     
        private appService2: AppService2,
        private appService3: AppService3) {
    }
}

This approach has at least 2 benefits as compared to the useValue: new AppService1() approach.

The first and the obvious one, if AppService1 has dependencies, they would be resolved automatically by the Angular's DI.

The second, often services that are not actually referenced anywhere from your app are some king of global configuration services. So, you can combine such service instantiation with a global configuration in a single source file. Here is an example. In this case, this is NgbDatepickerConfig service:

import { NgModule } from "@angular/core";

import {
    NgbDateAdapter, NgbDateNativeUTCAdapter, NgbDateParserFormatter, NgbDatepickerConfig, NgbDatepickerModule,
    NgbDropdownModule, NgbTabsetModule
} from "@ng-bootstrap/ng-bootstrap";

import { UsDateParserFormatter } from "./us-date-parser-formatter";

@NgModule({
    exports: [
        NgbDatepickerModule,
        NgbDropdownModule,
        NgbTabsetModule
    ],
    providers: [
        { provide: NgbDateAdapter, useClass: NgbDateNativeUTCAdapter },
        { provide: NgbDateParserFormatter, useClass: UsDateParserFormatter }
    ]
})
export class NgbImportsModule {
    public constructor(private readonly datepickerConfigService: NgbDatepickerConfig) {
        datepickerConfigService.minDate = { year: 1900, month: 1, day: 1 };
        datepickerConfigService.maxDate = { year: 2099, month: 12, day: 31 };
    }
}

In this example, NgbImportsModule was initially introduced just to re-export required modules from NGB to the rest of my app. But as my app features were building up, NgbImportsModule turned into a single place where certain parts of NGB are configured conveniently in one place.