Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 807
Estus Flask Avatar asked Oct 01 '16 00:10

Estus Flask


1 Answers

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) {}
}

As explained in this answer, APP_INITIALIZER shares some traits with config block, too, because it is used to configure services prior to component initialization and is susceptible to race conditions (for example, since APP_INITIALIZER is used to configure Router, injecting it into another APP_INITIALIZER will result in circular dependency).

like image 73
Estus Flask Avatar answered Oct 09 '22 22:10

Estus Flask