0

I have added AngularFire to my project. In its readme I was instructed to add the AngularFireModule.initializeApp(...) to the imports of my AppModule

In examples people use the environment constant, however, that is not an option because Deployment requires that there be a config file where it would then substitute variables when it deploys depending on the environment it deploys to.

I believe my config is loaded after the imports which means nothing is passed to the .initializeApp(...)

app.module.ts

import { AngularFireModule } from '@angular/fire';
import { AngularFireAuthModule } from '@angular/fire/auth';
...

export function initializeApp(appConfig: AppConfig) {
  return () => appConfig.load();
}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    ...,
    AngularFireModule.initializeApp(AppConfig.firebase),
    AngularFireAuthModule
  ],
  providers: [
    AppConfig,
    { provide: APP_INITIALIZER, useFactory: initializeApp, deps: [AppConfig], multi: true },
    ...,
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

app-config.ts

import { Injectable } from '@angular/core';
import { AppConfigModel } from './shared/models/app-config.model';
import { HttpClient } from '@angular/common/http';
import { environment } from '../environments/environment';

/**
 * This config pattern based off https://devblogs.microsoft.com/premier-developer/angular-how-to-editable-config-files/
 * which is referenced in Octopus Deploy blog here https://octopus.com/blog/javascript-configuration
 */
@Injectable()
export class AppConfig extends AppConfigModel {

  constructor(private http: HttpClient) {
    super();
  }

  /**
   * Loads the required config file from assets, depending on configName of environment.
   * This allows replacement of variables by Octopus Deploy
   */
  public load() {
    const jsonFile = `assets/config/config.${environment.configName}.json`;

    return new Promise<void>((resolve, reject) => {
      this.http.get(jsonFile).toPromise().then((response: AppConfigModel) => {
        this.mapConfigToProperties(response);
        resolve();
      }).catch((response: any) => {
        console.error('On config load', response);
        reject(`Could not config load file '${jsonFile}': ${JSON.stringify(response)}`);
      });
    });
  }

  private mapConfigToProperties(config: any) {
    Object.keys(config).forEach(key => {
      AppConfig[key] = config[key];
    });
  }
}

I would very much like to get this solved without having to change any build processes and simply being able to to use the AppConfig to retrive the config.

package.json

{
  ...,
  "dependencies": {
    "@angular/animations": "^6.1.10",
    "@angular/cdk": "^6.3.0",
    "@angular/common": "^6.0.3",
    "@angular/compiler": "^6.0.3",
    "@angular/core": "^6.0.3",
    "@angular/fire": "^5.3.0",
    "@angular/forms": "^6.0.3",
    "@angular/http": "^6.0.3",
    "@angular/platform-browser": "^6.0.3",
    "@angular/platform-browser-dynamic": "^6.0.3",
    "@angular/router": "^6.0.3",
    "core-js": "^2.5.4",
    "firebase": "^7.7.0",
    "moment": "^2.24.0",
    "ng-pick-datetime": "^6.0.16",
    "ng-pick-datetime-moment": "1.0.7",
    "ngx-image-cropper": "^2.0.2",
    "ngx-smart-modal": "^7.1.1",
    "ngx-textarea-autosize": "^2.0.3",
    "ngx-toastr": "^10.0.4",
    "rxjs": "6.3.3",
    "zone.js": "^0.8.26"
  },
  ...
}

main.ts

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { FirebaseOptionsToken, AngularFireModule } from '@angular/fire';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.log(err));

Salim
  • 439
  • 2
  • 8
  • Sounds like a good use case for [APP_INITIALIZER](https://angular.io/guide/dependency-injection-providers#predefined-tokens-and-multiple-providers). – Phix Jan 21 '20 at 21:49
  • Turns out APP_INITIALIZER does not necessarily run before the `.initializeApp(...)` is called. If you see above there already is an APP_INITIALIZER – Salim Jan 21 '20 at 21:58
  • Doh... my bad. Didn't see that. – Phix Jan 21 '20 at 21:58

1 Answers1

0

This can be achieved by providing parameters to the platformBrowserDynamic(...) which takes an array of providers similar to the array in the providers property of @NgModule.

We will solve this by using this parameter. We will feed in the parameter for the firebase config and then remove it from the import in the AppModule.

main.ts

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { FirebaseOptionsToken, AngularFireModule } from '@angular/fire';

if (environment.production) {
  enableProdMode();
}

// Get the config file
fetch(`assets/config/config.${environment.configName}.json`)
  .then(response => response.json())
  .then((response: any) => {
    // Here we add the additional provider
    platformBrowserDynamic([{provide: FirebaseOptionsToken, useValue: response.firebaseSettings}])
      .bootstrapModule(AppModule)
      .catch(err => console.log(err));
}).catch((response: any) => {
  console.error('On config load', response);
});

app.module.ts

import { AngularFireModule, FirebaseOptionsToken } from '@angular/fire';
import { AngularFireAuthModule } from '@angular/fire/auth';
...

export function initializeApp(appConfig: AppConfig) {
  return () => appConfig.load();
}

// initialize app with empty config
const angularFireImport = AngularFireModule.initializeApp({});

// remove the provider that we have provided for in the platforBrowserDynamic
angularFireImport.providers = angularFireImport.providers.filter(
  (provider: any) => provider.provide !== FirebaseOptionsToken
);


@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    ...,
    // import the module we constructed above without the provider
    angularFireImport,
    AngularFireAuthModule
  ],
  providers: [
    ...,
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

This answer was inspired by AngularFire2 App Init in Module Conflicts with Dynamic Config Data

Additional reading on providers and angular dependency injection, link

Salim
  • 439
  • 2
  • 8