7

I'm trying to inject a variable into forRoot({[...]}) method of an Angular module.

I got this variable in an asynchronous way, so I tried to get it before bootstrap Angular. (It comes from cordova.js)

The problem:

It seems that Angular import the modules (and call the 'forRoot' methods) before being bootstrapped.

Is there a way to achieve this ?

Thanks !


An example of what I tried:

app.module.ts

import {NgModule} from '@angular/core';
import {AgmCoreModule} from '@agm/core';

@NgModule({
  imports: [
    AgmCoreModule.forRoot({
      apiKey: window['device'].platform, // value is 'null' instead of 'browser' or something else
      libraries: ['places']
    })
  ],
  // [...]
  bootstrap: [AppComponent]
})

export class AppModule {
}

src/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';

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

document.addEventListener('deviceready', () => {
  console.log(window['device'].platform); // log 'browser'
  platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.log(err));
}, false);

/!\ Tips

The library I'm using (@agm/core) expect a string as apiKey, and not a function...

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Doubidou
  • 1,573
  • 3
  • 18
  • 35

2 Answers2

9

You can use APP_INITIALIZER to provide a factory which will be executed after module imports but before Bootstrap. So you can set the apiKey to any value and override it in the factory: Create a function to fetch die needed data and set the apiKey into the LAZY_MAPS_API_CONFIG Object. The Application bootstrap will continue once all the APP_INITIALIZERS have resolved.

import { NgModule, APP_INITIALIZER } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { AgmCoreModule, LAZY_MAPS_API_CONFIG, LazyMapsAPILoaderConfigLiteral } from '@agm/core';
import { AppComponent } from './app.component';
import { map } from 'rxjs/operators';

export function agmConfigFactory(http: HttpClient, config: LazyMapsAPILoaderConfigLiteral) {
  return () => http.get<{mapsApiKey: string}>("url").pipe(
    map(response => {
        config.apiKey = response.mapsApiKey;
        return response;
    })
  ).toPromise();
}

@NgModule({
  imports:      [ BrowserModule, HttpClientModule, AgmCoreModule.forRoot({ apiKey: "initialKey"}) ],
  declarations: [ AppComponent ],
  providers: [ {
    provide: APP_INITIALIZER, 
    useFactory: agmConfigFactory, 
    deps: [HttpClient, LAZY_MAPS_API_CONFIG], 
    multi: true} 
    ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }
A.Winnen
  • 1,680
  • 1
  • 7
  • 13
  • Thanks, it works perfectly ; I have already used APP_INITIALIZERS for other things, but I didn't think it can fit this case :) – Doubidou Nov 09 '18 at 16:52
  • 1
    Bit late for this answer, but I'm having the same issue (although with a different package). Does this only work because they expose `LAZY_MAPS_API_CONFIG`? The package I'm using takes in an object in the `forRoot()` which I want to populate with data from a HTTP query. – ADringer Mar 12 '19 at 14:05
  • 1
    This really helped me. Sure, it's a little bit of a hack, but Angular and Angular Google Maps don't provide a way to return a promise/observable for LAZY_MAPS_API_CONFIG. Keep in mind, you must provide an apiKey in your AgmCoreModule.forRoot({ apiKey: '' }), even if it's initially an empty string. – JWess Sep 12 '19 at 22:12
  • @JWess You dont need to provide a key, just the config object. Giving apiKey a value in forRoot will over write what you set in the agmConfigFactory method. – Mike_G Oct 29 '19 at 16:11
-1

For Populating value which is called async into the import for forRoot().

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { FormsModule } from "@angular/forms";
import { AppComponent } from './app.component';
import { MqttModule, IMqttServiceOptions } from "ngx-mqtt";
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { map } from 'rxjs/operators';

export const MQTT_SERVICE_OPTIONS: IMqttServiceOptions = {
  url: ""
}

export function wssConfig(http: HttpClient) {
  return () => http.get<{ data: string }>("url").pipe(
    map(response => {
      MQTT_SERVICE_OPTIONS.url = response.data;
      return response;
    })
  ).toPromise();
}

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    MqttModule.forRoot(MQTT_SERVICE_OPTIONS)
  ],
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: wssConfig,
      deps: [HttpClient],
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {

}