简体   繁体   中英

Angular: APP_INITIALIZER with GlobalErrorHandler

In our Angular app we need to load config settings from the server before initializing the app.

Using APP_INITIALIZER as such works great:

export function loadConfig(config: ConfigService) => () => config.load()

@NgModule({
    declarations: [AppComponent],
    imports: [BrowserModule,
        routes,
        FormsModule,
        HttpModule],
    providers: [
        ConfigService,
        {
            provide: APP_INITIALIZER,
            useFactory: loadConfig,
            deps: [ConfigService],
            multi: true
        }
    ],
    bootstrap: [AppComponent]
})
export class AppModule {
}

I then extended ErrorHandler to create a GlobalErrorHandler and wanted to tell Angular to use our GlobalErrorHandler, so I provided the ErrorHandler and told Angular to use the class GlobalErrorHandler.

    providers: [
            ConfigService,
            {
                provide: APP_INITIALIZER,
                useFactory: loadConfig,
                deps: [ConfigService],
                multi: true
            },        
            {
                provide: ErrorHandler,
                useClass: GlobalErrorHandler
            }
    ]

GlobalErrorHandler needs information from the config settings and I need to instantiate GlobalErrorHandler AFTER the setting's promise is resolved. In the set up above, GlobalErrorHandler is created before my call is made to the server for the configuration values.

How can I load the configuration values from the server, and THEN instantiate the GlobalErrorHandler and use the configuration values?

It doesn't seem to be possible with the way you want it done. Here is the relevant code that instantiates the ErrorHandler and calls APP_INITIALIZER callbacks:

  private _bootstrapModuleFactoryWithZone<M>(moduleFactory: NgModuleFactory<M>, ngZone?: NgZone):
      Promise<NgModuleRef<M>> {
    ...
    return ngZone.run(() => {
      const ngZoneInjector =
          ReflectiveInjector.resolveAndCreate([{provide: NgZone, useValue: ngZone}], this.injector);
      const moduleRef = <InternalNgModuleRef<M>>moduleFactory.create(ngZoneInjector);

      !!! ------------- here ErrorHandler is requested and instantiated ------------ !!!
      const exceptionHandler: ErrorHandler = moduleRef.injector.get(ErrorHandler, null);
      if (!exceptionHandler) {
        throw new Error('No ErrorHandler. Is platform module (BrowserModule) included?');
      }
      ...
      return _callAndReportToErrorHandler(exceptionHandler, () => {
        const initStatus: ApplicationInitStatus = moduleRef.injector.get(ApplicationInitStatus);

       !!! ------------ and here your APP_INITIALIZER callbacks are executed ---------- !!!
        initStatus.runInitializers();
        return initStatus.donePromise.then(() => {
          this._moduleDoBootstrap(moduleRef);
          return moduleRef;
        });
      });
    });
  }

As you can see the ErrorHanlder is requested from the injector before your callbacks are triggered. And there's no other way to perform server request before.

I believe your only option is to make a request before the application starts and then once you get the response add the ErrorHanlder to module providers and bootstrap the app:

// app.module.ts
export class AppModule {}

export const decoratorDescriptor = {
  imports: [BrowserModule, FormsModule, ReactiveFormsModule],
  declarations: [AppComponent, DebounceDirective, ExampleComponent],
  providers: [],
  bootstrap: [AppComponent]
};


// main.ts
const http = injector.get(Http);
http.get('assets/configs/configuration.json').subscribe((r) => {
  const ErrorHandler = configureErrorHandler(r.json());
  decoratorDescriptor.providers.push(ErrorHandler);
  const decorated = NgModule(decoratorDescriptor)(AppModule);
  platformBrowserDynamic().bootstrapModule(decorated);
});

See this answer to learn how to configure the http .

Thought the answer might help someone who needs it...

I was able to resolve the issue by giving the APP_INITIALIZER as a dependency in the GlobalErrorHandler section. This made the GlobalErrorHandler instantiation to wait till the APP_INITIALIZER was available, thus solving my issue.

providers: [
            ConfigService,
            {
                provide: APP_INITIALIZER,
                useFactory: loadConfig,
                deps: [ConfigService],
                multi: true
            },        
            {
                provide: ErrorHandler,
                useClass: GlobalErrorHandler,
                deps: [APP_INITIALIZER]
            }
    ]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM