2

I've been trying to deploy on DevOps an Angular 8 application and use configuration inside .json files in order to not re-build the entire application for different environments.

I used these 2 posts in order to create all the configurations:

Continuously Deploying Angular to Azure App Service with Azure DevOps

and a Stack overflow answer:

App.settings - the Angular way?

Note that I'm not interested on using the environment.ts way, as this way will require me to re-build the solution for each environment.

So, I prepared all my code like this:

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule
    ],
    providers: [
        {
             provide: APP_INITIALIZER,
             useFactory: (appConfigService: ConfigService) => {
             return () => {
                //Make sure to return a promise!
                return appConfigService.loadAppConfig();
             };
          },
          deps: [ConfigService],
          multi: true
       }
    ],
    bootstrap: [AppComponent]
 })
 export class AppModule {}

My ConfigService.ts:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class ConfigService {
private appConfig: any;

constructor(private http: HttpClient) {}

loadAppConfig() {
  return this.http.get('./assets/appconfig.json')
    .toPromise()
    .then(config => {
      this.appConfig = config;
    });
}

get apiBaseUrl() {
    if (!this.appConfig) {
      throw Error('Config file not loaded!');
    }

    return this.appConfig.apiUrl;
  }
}

And then, the main object that needs to load appconfig.json information:

  export class ApiService {
  apiUrl: string;

     constructor(private readonly httpClient: HttpClient,
          private configService: ConfigService) { 
            this.apiUrl = this.configService.apiBaseUrl;
     }

     ngOnInit() {
       this.apiUrl = this.configService.apiBaseUrl;
     }    
  }

But then, while loading the app, this message arises:

enter image description here

If I debug the app, the appsettings.json file is loading info, but then it looks like the angular init is happening before loading the appsettings.

What I'm doing wrong?

Sonhja
  • 8,230
  • 20
  • 73
  • 131

2 Answers2

1

You can return a Promise and resolve it inside the subscribe callback of HTTP request as followings:

loadAppConfig() {
  return new Promise((resolve) => {
     this.http.get('./assets/appconfig.json').subscribe(config => {
        this.appConfig = config;
        resolve();
     })
  });
}
Harun Yilmaz
  • 8,281
  • 3
  • 24
  • 35
  • Not sure on what files to change.... I changed the loadAppConfig() as you said, and returning an Promise, but not understanding what else to change and make it work – Sonhja Feb 07 '20 at 09:30
  • Can you please create a project on [stackblitz](https://stackblitz.com/) to reproduce the problem so that I can help you further? – Harun Yilmaz Feb 07 '20 at 09:33
  • I fixed some of the errors in the project. I found out that stackblitz currently is not supporting the HTTP request to local files so I replaced it with a placeholder. It is working now: https://stackblitz.com/edit/angular-eprwua – Harun Yilmaz Feb 07 '20 at 10:03
  • then apiUrl is undefined... so it's not loading during app initialize. That's my problem. Locally I didn't have errors, maybe I didn't make it work properly, but the exact problem is that the hello.component doesn't load the apiUrl properly. See that the first console.log loads data (for me was working the same), but then, when you try to access it on hello.component, you don't have that data. – Sonhja Feb 07 '20 at 10:13
  • Any ideas on why it's not loading it on APP_INITIALIZER? – Sonhja Feb 07 '20 at 10:30
  • `apiUrl` property is `undefined` because the JSON placeholder does not have it. I created an external JSON file which has `apiUrl` property. Please have a look at it again. – Harun Yilmaz Feb 07 '20 at 10:56
0

So everything was well configured except from one important thing: Constructors are used only to inject dependencies. So at that point, I cannot make this:

constructor(private readonly httpClient: HttpClient,
      private configService: ConfigService) { 
        this.apiUrl = this.configService.apiBaseUrl;
 }

SOLUTION

I removed then the line inside the constructor:

constructor(private readonly httpClient: HttpClient,
      private configService: ConfigService) { 
 }

And just called the apiBaseUrl wherever I needed it and it worked:

public get<T>(url: string): Observable<T> {
    return this.httpClient.get<T>(`${this.configService.apiBaseUrl}${url}`);
}
Sonhja
  • 8,230
  • 20
  • 73
  • 131