8

I am using APP_INITIALIZER for fetching config from json and it works fine. Earlier i was having an auth guard as part of application and it used to work great.

Then we split the authorization logic into a library and it works fine if we call forRoot() or if we give static values to config but to allow dynamic configuration I used the InjectionToken from library to provide config without calling forRoot.

The code for app.module.ts is like this:

let authConfig: any; export function authConfigFactory() { return authConfig; } export function appInitializerFn(appConfigService: AppConfigService) { return () => { return appConfigService.loadAppConfig().then(() => { authConfig = { clientId: appConfigService.getConfig().CLIENT_ID, tokenEndpoint: appConfigService.getConfig().TOKEN_URL, redirectURL: "http://localhost", }; }); }; }; @NgModule({ ..... imports: [ .. AuthLib ], providers: [ AppConfigService, { provide: APP_INITIALIZER, useFactory: appInitializerFn, multi: true, deps: [AppConfigService] }, AuthLibService, { provide: 'authConfig', useFactory: authConfigFactory, deps: [] }, ..... ] bootstrap: [AppComponent] }) export class AppModule { } 

Now authConfigFactory is getting called way before appInitializerFn resulting in undefined and if i add async to authConfigFactory to prevent return statement till its defined then empty values are fed to AuthGuard resulting in invalid token url.

If i provide values manually in appInitializerFn before calling for promise the values gets translated and things work normally. But at that stage dynamic values are not present.

Code for app-config.service.ts:

import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { environment } from 'src/environments/environment'; import { AppConfig } from '../_models/app-config'; import { LoggingService } from './logging.service'; @Injectable() export class AppConfigService { static appConfig : AppConfig; private dataLoc: string; constructor(private http: HttpClient, private logger: LoggingService) { } loadAppConfig() { if(environment.production){ this.dataLoc = '/assets/data/appConfig.json'; } else{ this.dataLoc = '/assets/data/appConfig-dev.json'; } return new Promise<void>((resolve, reject) => { this.http.get(this.dataLoc).toPromise().then((response: AppConfig) => { AppConfigService.appConfig = <AppConfig>response; resolve(); }).catch((response: any) => { reject(`Could not load file '${this.dataLoc}': ${JSON.stringify(response)}`); }); }); } getConfig() { return AppConfigService.appConfig; } } 

Anything missing from library or code to make this work ?

I am bit new to Angular regime, let me know even in case i did some stupid mistake.

2
  • hey there, i tried to reproduce your issue in this repo but i couldn't. my attempt at least verifies that your logic regaring APP_INITIALIZER is correct. so the the cause of problem must be somewhere else. i suggest taking a look at the repo above and provide any further ideas to track the problem. i am suspecting AuthLib and AuthLibService might have something. Commented Jul 8, 2019 at 17:48
  • Thanks man. Your stackblitz example helped me in isolation of issue. Commented Jul 12, 2019 at 13:11

1 Answer 1

12

So found the reason and solution. Posting it for anyone come stumbling to this question.

Since APP_INITIALIZER is getting invoked properly. The thing i missed was that i am using "HttpClient" to fetch the config which in-turn is invoking any HTTP_INTERCEPTORS defined in ROOT and eventually resulting in initialization of Authentication Service which in turn needs auth config as token injection in constructor.

Hence token is getting injected even before value are fetched causing it to go undefined/empty.

The solution was although pretty easy, either we can use

  1. HttpBackend to fetch json or
  2. Fetch the json in main.ts or
  3. Prevent interceptor to be invoked for that json path.

In my case point 3 was not possible as i want tight control on all communication, while Point 2 is bit clumsy. We went with approach 1. Sharing modified code for reference.

import { Injectable } from '@angular/core'; import { HttpClient, HttpBackend } from '@angular/common/http'; import { environment } from 'src/environments/environment'; import { AppConfig } from '../_models/app-config'; import { LoggingService } from './logging.service'; @Injectable() export class AppConfigService { static appConfig : AppConfig; private dataLoc: string; constructor(private handler: HttpBackend, private logger: LoggingService) { } loadAppConfig() { if(environment.production){ this.dataLoc = '/assets/data/appConfig.json'; } else{ this.dataLoc = '/assets/data/appConfig-dev.json'; } return new HttpClient(this.handler).get(this.dataLoc) .toPromise() .then((data:AppConfig) => { this.logger.info(data); AppConfigService.appConfig = data; }); } getConfig() { return AppConfigService.appConfig; } } 

Thanks to @ysf, your minimal example gave me the idea that if its working in general then something else is invoking the authconfig during intialization.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.