Make sure a service is instantiated

11,514

Solution 1

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(
        appService1: AppService1,     
        appService2: AppService2,
        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(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.

Solution 2

As mentioned in various comments, one option is simply to directly instantiate such services, this would look like

// app.module.ts

@NgModule({
  providers: [
    { provide: AppService1, useValue: new AppService1() },
    { provide: AppService2, useValue: new AppService2() },   
    { provide: AppService2, useValue: new AppService3() }
  ]
}) export class AppModule {}

You might be tempted to avoid direct instantiation because it goes against the everything is handled by the Injector mindset, but it doesn't break DI or testability for a number of reasons.

One reason is that the use of ES Modules, combined with the use of a configurable loader, and the expressiveness of TypeScript's structurally typed nature, allow even these sorts of dependencies to be swapped for test doubles at runtime by a leveraging a loader like SystemJS.

That said, if you find yourself doing this very frequently, you may need to re-evaluate your app structure, but in general there are plenty of use cases for which this solution is the simplest. Also, it can break cycles in the injector.

By break cycles in the injector, I mean that it is possible to capture an instance of a required service that would otherwise need to be injected in the constructor of another service by simply referencing its value in the expression specified for useValue. This technique is even more useful with useFactory. Regardless, this is fairly uncommon but can be a useful workaround.

Solution 3

Services are instantiated when you provide them.

What you are doing in the constructor is injecting an existing service instance into your class so that it can be used.

There are three ways to instantiate or provide a service:

One is in the app module.

@NgModule ({
    providers: [ myservice1, myservice2],
})

This instantiates the service and makes it available throughout the application.

You can also provide it in a feature module, or shared module, and it accomplishes the same thing. If you provide it in the app module, then provide the same service in a feature module that is lazy loaded, it instantiates a second one that becomes globally available. The first one disappears into the ether.

If you want to instantiate a service for a specific component, and possibly have multiple instantiations one for each component, then you provide it in the component metadata.

@Component ({
     providers: [myservice]
})

Then you would inject it using the constructor.

Share:
11,514
Ben Winding
Author by

Ben Winding

Updated on July 15, 2022

Comments

  • Ben Winding
    Ben Winding almost 2 years

    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?

  • Aluan Haddad
    Aluan Haddad over 7 years
    This is an oversimplification. Also, you left out the most obvious way a service is instantiated, by instantiating it.
  • Ben Winding
    Ben Winding over 7 years
    Listing it in the providers of any part of the app, does not seem to instantiate the service, as far as I can tell, the service also has to be injected through the constructor.
  • Paul Samsotha
    Paul Samsotha over 7 years
    One way to make sure they are instantiated is to do providers: [ { provide: AppService, useValue: new AppService() } ]
  • Aluan Haddad
    Aluan Haddad over 7 years
    They are instantiated lazily as requested so this is expected. Give peeskillet 's suggestion a try, that is what I would recommend. @peeskillet, your username is annoying to write.
  • Paul Samsotha
    Paul Samsotha over 7 years
    @AluanHaddad when you type @p, there should be a pop up with the auto-complete name. Click it next time :P
  • Readren
    Readren over 7 years
    What if AppService1 uses another service? For example, when it subscribes to an observable offered by the other service. How can we reference an instance of another service in the useValue property expression?
  • Aluan Haddad
    Aluan Haddad over 7 years
    If your service has dependencies you need to use a different pattern.
  • Luca Regazzi
    Luca Regazzi about 6 years
    @AluanHaddad What pattern? I have the same issue. I need to globally instantiate a service but the constructor depends on another service.
  • Alexander Abakumov
    Alexander Abakumov over 5 years
    @LucaRegazzi: One of the options for your case is described in my answer.
  • Ben Winding
    Ben Winding about 5 years
    This seems to be the best solution, as it also allows the dependency injection for the services too.
  • Alexander Abakumov
    Alexander Abakumov about 5 years
    @BenWinding Thank you, I'm happy this still seems to be helpful to people! Could you consider making it as an accepted answer then, please?
  • Bernoulli IT
    Bernoulli IT about 3 years
    Best solution, answer by @AluanHaddad had my favor but it didn't work out 😟 especially not because the desired service to instantiate itself needed other services injected. This solution worked and is pretty clean. Just inject the desired "nowhere injected" services to be injected in the constructor of the module where they are declared 👍🏼
  • Nam Le
    Nam Le almost 3 years
    I would like to add that "private" isn't needed. The service will still be instantiated even if it's just a constructor argument. Also in case your project use some sort of linter that reports it as "is declared but its value is never read", with a grayish text that makes us think it's safe to remove: be sure to include a comment saying what you did was intentional. Cheers!
  • Alexander Abakumov
    Alexander Abakumov almost 3 years
    @NamLe Thank you, privates were typos. Fixed.