5

I wonder how can I run a service from a module when it's imported without any manual service injection and run, just like the RouterModule does.

@NgModule({
  imports: [

    BroserModule,
    MyModuleWithServicesThatShouldAutoRun,

  ]
})
export class AppModule { }
Machado
  • 8,965
  • 6
  • 43
  • 46

2 Answers2

9

Disclaimer: this is based on another answer in which was not accepted as the right answer because there was a simpler and easier way to do what the OP needed.

You can use the APP_INITIALIZER injection token to run initialization code before any of your other application code runs.

APP_INITIALIZER is defined in @angular/core and you include it like this:

import { APP_INITIALIZER } from '@angular/core';

APP_INITIALIZER is an OpaqueToken that references the multi provider ApplicationInitStatus service. It supports multiple dependencies and you can use it in your providers list multiple times, e.g.:

@NgModule({
  providers: [
    MyService,
    {
      provide: APP_INITIALIZER,
      useFactory: (service: MyService) => function() { return service.init(); },
      deps: [MyService],
      multi: true
    }]
})
export class AppModule { }

This provider declaration is telling the ApplicationInitStatus class to run the MyService.init() method. init() returns a Promise and ApplicationInitStatus blocks the app startup until the Promise resolves.

export class MyService {

  // omitted other methods for brevity

  init(): Promise<any> {

    // start some observers, do the stuff you need

    // you can even request something via http
    return this.httpClient
      .get('https://someurl.com/example')
      .toPromise()
  }

}

In that way, anything inside init will run and block the application load until the Promise resolves.

Be aware that this can increase the up-front load time for you app by however long the init() method takes. For loading content before openning a route you should use a resolver instead.

Sources:

Machado
  • 8,965
  • 6
  • 43
  • 46
  • I'd like to call an asynchronous service that returns a subscription to an observable. So I need to save the subscription reference into a variable so as to unsubscribe when the whole thing goes down. And I would prefer to have this not delay the application startup as it can be run after startup time. – Stephane Apr 25 '20 at 12:14
3

What the RouterModule does is use a forRoot() and forChild() (sometimes called forFeature()) method, which is a convention to configure your module when it is loaded. A good description on how to implement that can be found here.

If you take a look at the RouterModule's source code, you will see that forRoot() and forChild()configure the module like this:

providers: [
        ROUTER_PROVIDERS,
        provideRoutes(routes),
        {
          provide: ROUTER_FORROOT_GUARD,
          useFactory: provideForRootGuard,
          deps: [[Router, new Optional(), new SkipSelf()]]
        },
       // many more, omitted
]

In that place you could also add an APP_INITIALIZER as described in other answers, which would effectivly lead to your importing Module to look like this:

@NgModule({
  imports: [

    BroserModule,
    MyModuleWithServicesThatShouldAutoRun.forRoot( /* some config, maybe */),

  ]
})
export class AppModule { }
pascalpuetz
  • 5,238
  • 1
  • 13
  • 26