estus estus - 2 months ago 20
Javascript Question

Force service instantiation in Angular 2 sub-module (an alternative to AngularJS run block)

I have a service in sub-module that wraps some third-party module, instantiates and initializes its service to prepare for use within app.

@Injectable()
class SubmoduleInitializerService {
constructor (thirdPartyService: ThirdPartyService) {
thirdPartyService.initialize(...);
...
}
}

@NgModule({
imports: [ThirdPartyModule],
exports: [ThirdPartyModule],
providers: [
ThirdPartyService,
SubmoduleInitializerService
]
})
class AppSubmodule {}


ThirdPartyService
isn't injected in app directly but is used by other
ThirdPartyModule
units, so as long as
SubmoduleInitializerService
is injected in the same injector as
ThirdPartyService
or parent injector, everything is fine:

export class AppComponent {
constructor(
/* DO NOT REMOVE! BAD THINGS HAPPEN! */
submoduleInitializerService: SubmoduleInitializerService
) {}
...
}


It was proven to be a lousy pattern because it is not obvious why
SubmoduleInitializerService
should stay injected in
AppComponent
if it's not used neither in class nor in template (was accidentally removed once already).

Basically
AppSubmodule
module needs an alternative to Angular 1.x
angular.module(...).run(...)
block.

What are the options here?

Answer

APP_INITIALIZER (undocumented) service plays the role of AngularJS config/run blocks reasonably well in Angular 2 (not counting the feature of asynchronous initialization).

For noop intialization block that just eagerly instantiates SubmoduleInitializerService it is:

@NgModule({
    imports: [ThirdPartyModule],
    exports: [ThirdPartyModule],
    providers: [
        ThirdPartyService,
        SubmoduleInitializerService,
        {
            provide: APP_INITIALIZER,
            useFactory: () => () => {},
            deps: [SubmoduleInitializerService],
            multi: true
        }
    ]
})
class AppSubmodule {}

Since APP_INITIALIZER is multi-provider, it allows to have several initialization functions per application that follow the order in which the modules are being loaded.

For synchronous initialization the shorter (and probably more appropriate) alternative is to inject the service into module's constructor:

@NgModule({
    imports: [ThirdPartyModule],
    exports: [ThirdPartyModule],
    providers: [
        ThirdPartyService,
        SubmoduleInitializerService
    ]
})
class AppSubmodule {
    constructor(sis: SubmoduleInitializerService) {}
}
Comments