devqon
devqon

Reputation: 13997

Angular APP_INITIALIZER circular dependencies at runtime

I have a circular dependency (at run-time!) problem, which I don't see how to resolve.

I have registered an APP_INITIALIZER load function, which loads a config.json file. This config file contains among others the LogLevel for NGXLogger. The config file is loaded through angular's HttpClient.

Besides that, I have registered an HttpInterceptor, which uses the NGXLogger.

It seems that angular tries to resolve everything in this order:

  1. APP_INITIALIZER, so run this function first
  2. Load function of the initializer triggers a http call, so first run through the interceptor
  3. Load dependencies of the HttpInterceptor => NGXLogger
  4. Configure providers for the NGXLogger => based on the configuration file in APP_INITIALIZER
  5. Throw error, config is undefined

How can I resolve this circular dependencies? Is there a way to completely skip the interceptor on the APP_INITIALIZER http call?

CoreModule.ts:

export function appConfigFactory(configService: AppConfigService) {
  return () => configService.load();
}

export function loggerConfigFactory(configService: AppConfigService): {
  return () => {
    level: (NgxLoggerLevel as any)[configService.config.logLevel] || NgxLoggerLevel.ERROR,
  };
}

@NgModule({
  ...
  providers: [{
    provide: APP_INITIALIZER,
    useFactory: appConfigFactory,
    deps: [AppConfigService],
    multi: true
  }, {
    provide: NgxLoggerConfig,
    useFactory: loggerConfigFactory,
    deps: [AppConfigService]
  }]
})
export class CoreModule {}

AppConfigService.ts:

export class AppConfigService {
  constructor(private http: HttpClient) {}

  load() {
    return this.http
      .get<AppConfig>(`/assets/configuration/${environment.configFile}`)
      .pipe(
        tap((config: any) => {
          this.config = config;
        })
      )
      .toPromise();
  }
}

FakeBackendInterceptor:

@Injectable()
export class FakeBackendInterceptor implements HttpInterceptor {
    constructor(private configService: AppConfigService, private logger: NGXLogger) { }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // config is undefined here the first time
        if (!this.configService.config || !this.configService.config.mock === 'true') {
            return next.handle(request);
        }
        this.logger.info('Intercepting request with mock');
        // do things
        ...
        return next.handle(request);
    }
}

Upvotes: 2

Views: 1015

Answers (1)

edkeveked
edkeveked

Reputation: 18381

You can use HttpBackend to skip the interceptor in the config service

constructor(
    private http: HttpClient,
    private httpBackend: HttpBackend) { }

And in the load method

const httpClient = new HttpClient(this.httpBackend);
httpClient.get<AppConfig>(`/assets/configuration/${environment.configFile}`)
.pipe(
    tap((config: any) => {
      this.config = config;
    })
  )
  .toPromise();

Upvotes: 4

Related Questions