Angular-google-maps: how to initilize the module with variable apiKey

Created on 3 Feb 2017  ·  12Comments  ·  Source: SebastianM/angular-google-maps

I have the google api key in a singleton configuration service (a way to share the app configuration accross all the application).

How can I pass the apiKey variable the module inside @NgModule?

`@NgModule({

imports: [
BrowserModule,
CommonModule,
FormsModule,
AgmCoreModule.forRoot({
apiKey: 'YOUR_KEY' // <-- I cannot use a constant!!! How can I pass a variable?
})
],
providers: [],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})`

stale discussion / question

Most helpful comment

Depends on what kind of build tool you are using (e.g. angular-cli or custom webpack or ...) and where the variable should come from.
This can be a little tricky. Specially when you also want to use AOT compiling, which prevents too much dynamic code inside the @NgModule.

The solution I currently implemented is overriding the 'lazy config provider' in my main app.module.

In the module where I actually use the map, I use an empty config in the forRoot:

AgmCoreModule.forRoot()

In my app.module.ts:
import the injection token:

import { LAZY_MAPS_API_CONFIG }
  from 'angular2-google-maps/core/services';

Add to providers in @NgModule:

providers: [
    {provide: LAZY_MAPS_API_CONFIG, useClass: GoogleMapsConfig}
  ],

And implement that 'GoogleMapsConfig' class, which should implement the LazyMapsAPILoaderConfigLiteral interface (from ' from 'angular2-google-maps/core/services').

@Injectable()
class GoogleMapsConfig {
  apiKey: string;
...
  constructor() {
    apiKey = getMyApiKeyFromSomewhere()
...
  }
}

In that injectable I can inject other services and read the config from somewhere.
(e.g. if you use angular-cli you can import environments there). Or maybe read out the domain from the browser... or call an serverside API...

All 12 comments

Depends on what kind of build tool you are using (e.g. angular-cli or custom webpack or ...) and where the variable should come from.
This can be a little tricky. Specially when you also want to use AOT compiling, which prevents too much dynamic code inside the @NgModule.

The solution I currently implemented is overriding the 'lazy config provider' in my main app.module.

In the module where I actually use the map, I use an empty config in the forRoot:

AgmCoreModule.forRoot()

In my app.module.ts:
import the injection token:

import { LAZY_MAPS_API_CONFIG }
  from 'angular2-google-maps/core/services';

Add to providers in @NgModule:

providers: [
    {provide: LAZY_MAPS_API_CONFIG, useClass: GoogleMapsConfig}
  ],

And implement that 'GoogleMapsConfig' class, which should implement the LazyMapsAPILoaderConfigLiteral interface (from ' from 'angular2-google-maps/core/services').

@Injectable()
class GoogleMapsConfig {
  apiKey: string;
...
  constructor() {
    apiKey = getMyApiKeyFromSomewhere()
...
  }
}

In that injectable I can inject other services and read the config from somewhere.
(e.g. if you use angular-cli you can import environments there). Or maybe read out the domain from the browser... or call an serverside API...

I just tried the above. It manages to circumvent AoT errors, but the API key isn't being passed to Google Maps – so it returns key missing error.

In my use case, I'm using the extended-define-webpack-plugin to add a compile-time global config settings (API keys that can change based on build type etc).

import { LAZY_MAPS_API_CONFIG, LazyMapsAPILoaderConfigLiteral } from 'angular2-google-maps/core/services';

@Injectable()
export class GoogleMapsConfig implements LazyMapsAPILoaderConfigLiteral {
  apiKey: string = CONFIG.googleMapsAPIKey;
}

@NgModule({
  declarations: [...],
  imports: [...],
  providers: [{provide: LAZY_MAPS_API_CONFIG, useClass: GoogleMapsConfig}],
  bootstrap: [AppComponent]
})
export class AppModule {}

And, in my lazy loaded module where I'm using the map, calling AgmCoreModule.forRoot()

I am using angular 4 and CLI 1.0.2, I am trying to get the above code by @kyranjamie but I cannot get this to work, what we are trying is basically the same set up a different key for prod vs. dev

this is my code, what am I doing wrong?

`
@Injectable()
export class GoogleMapsConfig implements LazyMapsAPILoaderConfigLiteral {
public apiKey: string;
constructor() {
if (environment.production) {
this.apiKey = KEY INSERTED HERE
};
}
}
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
HttpModule,
AppRoutingModule,
NgbModule.forRoot(),
NouisliderModule,
AgmCoreModule.forRoot()
],
providers: [
{ provide: LAZY_MAPS_API_CONFIG, useClass: GoogleMapsConfig }
],
bootstrap: [AppComponent]
})
export class AppModule {

}
`

Are you sure that if (environment.production) will ever resolve to true?

I have resolved this thanks for your feedback

@daBishMan or @kyranjamie , If you guys ever figured this out, could you post your final solution? @kyranjamie , my issue appears to be similar to yours,... on a product build where AOT is concerned, agm-map initialization in the request to maps.google.com,... no URL API-KEY parameter is being sent. Therefore, it comes back with "missing api key" errors.

@jorrit-wehelp, thank you for your guidance. I am including below what is working for me....

import {Injectable} from "@angular/core";
import {LazyMapsAPILoaderConfigLiteral} from "@agm/core";
import {Config} from "../providers/config";

@Injectable()
export class MapsConfig implements LazyMapsAPILoaderConfigLiteral{
  public apiKey: string
  public libraries: string[]
  constructor(config: Config) {
    this.apiKey = config.get("MAP_API_JS_KEY")
    this.libraries = ['places']
    console.log("lazy map init with " + this.apiKey)
  }
}

in my main @ngmodule, I have this in providers...

    {
      provide: LAZY_MAPS_API_CONFIG,
      useClass: MapsConfig,
      deps: [Config]
    }

The reference to the Config class is a build-time webpack variable-replace which setups of a map of variables used in Dev or Prod modes.

Hello

I was able to achieve the dynamic key, as by @jorrit-wehelp comments, but in my scenario after the user logout and loggedIn the agmKey value is not updating and not able to see the map

what I did was like in our module component we added the following code
So I moved the class GoogleMapsConfig and {provide: LAZY_MAPS_API_CONFIG, useClass: GoogleMapsConfig} inside map @component

```...
import { LAZY_MAPS_API_CONFIG , LazyMapsAPILoaderConfigLiteral} from '@agm/core';
import {AuthService} from '@pl-core/_services';

@Injectable();
class GoogleMapsConfig implements LazyMapsAPILoaderConfigLiteral {
public apiKey: string ;
constructor() {
console.log('INSIDE MAPs'); //This is not displayed in console
this.apiKey = _authService.currentUser() && _authService.currentUser().agmKey ? _authService.currentUser().agmKey : '';
_authService.getMapKey$.subscribe((key) => {
this.apiKey = key;
})
}
}

@Component({
selector: 'map-comp',
templateUrl: './dyn-map.component.html',
styleUrls: ['./dyn-map.component.scss'],
providers: [{provide: LAZY_MAPS_API_CONFIG, useClass: GoogleMapsConfig}],
encapsulation : ViewEncapsulation.None
})

```
after we logout and login with an other user the control is not coming inside the GoogleMapsConfig class what can I do to solve this issue??

I had @jorrit-wehelp's solution working but only intermittently because I had a race condition where my service that returns the api key would not always return it in time for the element to try and render. In order to force to wait until the api key was returned I simply did this:

constructor of my map component:

constructor(private envService: EnvironmentService) {
    this.envService.environment$.subscribe(environment => {
      // setTimeout is to wait a beat since my GoogleMapsConfig receives
      // the key at the same instant.
      setTimeout(() => this.gmapsApiKeyReady = true);
    });
  }

Then in the markup:

<agm-map *ngIf="gmapsApiKeyReady" ...></agm-map>

I don't know if this will apply to everyone or if this is more of a case by case basis, but I discovered this is the result of Angular's AOT compilation process, as opposed to an issue with agm itself. Here is an overview of the situation I had.

I wanted to make the API key config driven instead of a hard coded string, such that moving through different environments would allow me to use different API keys and monitor the API's usage. In order to do this, I had my .NET back-end inject some JavaScript with the value I needed into the global namespace, stored in Example.Namespace.GoogleMapsApiKey. With that in mind, here was my TypeScript code.

// typings.d.ts
declare namespace Example.Namespace {
    export const GoogleMapsApiKey: string;
}

// app.module.ts
import { AgmCoreModule } from "@agm/core";
import { NgModule } from "@angular/core";

@NgModule({
    // ...
    imports: [
        AgmCoreModule.forRoot({
            apiKey: Example.Namespace.GoogleMapsApiKey,
        })
    ],
    // ...
})
export class AppModule {}

I thought creating a type definition for some const global variable with the value I needed would get included during the AOT build process. However, as it turns out the AOT compiler only supports a subset of TypeScript tools, which prevented the variable from getting included at compilation time. Effectively what was happening was an "Only initialized variables and constants" error without an error getting emitted from the Angular CLI. Once I made the switch to use some kind of LAZY_MAPS_API_CONFIG provider using a class I was able to successfully .

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

DeveloperAdd007 picture DeveloperAdd007  ·  3Comments

stot3 picture stot3  ·  3Comments

matishw picture matishw  ·  3Comments

marcelinobadin picture marcelinobadin  ·  3Comments

maneesht picture maneesht  ·  3Comments