Reputation: 367
I'm using @agm/[email protected]
and Angular 14.2.10
. The api key is provided by the server, so I created an EnvService
which has the following method
/**
* get google map api key
* @returns Observable<any>
*/
getMapsKey(): Observable<EnvResponse> {
return this.getRequest(this.urls.base + '/env/maps-key').pipe(takeUntil(this.ngUnsubscribe));
}
Which returns the following json response
{
"googleMapsApiKey": "apiKey"
}
I followed this guide https://gist.github.com/msaxena25/fa59c2130642de3ef6330c6cff2ce6c2, but I could not get it to work. When I set { provide: MapsAPILoader, useClass: CustomLazyAPIKeyLoader}
on my AppModule providers, I get the following errors on my PdoProfileComponent:
ERROR NullInjectorError: R3InjectorError(PdoProfileModule)[MapsAPILoader -> MapsAPILoader -> undefined -> undefined -> undefined]:
NullInjectorError: No provider for undefined!
at NullInjector.get (core.mjs:6359:27)
at R3Injector.get (core.mjs:6786:33)
at R3Injector.get (core.mjs:6786:33)
at R3Injector.get (core.mjs:6786:33)
at injectInjectorOnly (core.mjs:4782:33)
at Module.ɵɵinject (core.mjs:4786:60)
at Object.CustomLazyAPIKeyLoader_Factory [as factory] (custom-apikey-loader.service.ts:14:45)
at R3Injector.hydrate (core.mjs:6887:35)
at R3Injector.get (core.mjs:6775:33)
at R3Injector.get (core.mjs:6786:33)
pdo-profile.component.ts:132 ERROR Error: NG0200: Circular dependency in DI detected for MapsAPILoader. Find more at https://angular.io/errors/NG0200
at throwCyclicDependencyError (core.mjs:240:11)
at R3Injector.hydrate (core.mjs:6883:13)
at R3Injector.get (core.mjs:6775:33)
at R3Injector.get (core.mjs:6786:33)
at ChainedInjector.get (core.mjs:13769:36)
at lookupTokenUsingModuleInjector (core.mjs:3293:39)
at getOrCreateInjectable (core.mjs:3338:12)
at ɵɵdirectiveInject (core.mjs:10871:12)
at Module.ɵɵinject (core.mjs:4786:60)
at NodeInjectorFactory.GoogleMapsAPIWrapper_Factory [as factory] (agm-core.js:222:126)
It works when I remove { provide: MapsAPILoader, useClass: CustomLazyAPIKeyLoader}
from my AppModule providers, but I won't be able to set my google map api key dynamically. How can I achieve my goal?
AppModule
@NgModule({
imports: [
...,
AgmCoreModule.forRoot({
apiKey: 'initialKey',
libraries: ['places']
}),
],
declarations: [AppComponent, AdminLayoutComponent, AuthLayoutComponent, PdoLayoutComponent],
providers: [
InterceptService,
{
provide: HTTP_INTERCEPTORS,
useClass: InterceptService,
multi: true,
},
{ provide: LocationStrategy, useClass: HashLocationStrategy },
{ provide: MapsAPILoader, useClass: CustomLazyAPIKeyLoader} // I don't get error when I remove this
],
bootstrap: [AppComponent],
})
export class AppModule {}
PdoProfileModule (Child Module)
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(PdoProfileRoutes),
FormsModule,
SharedModule,
MatTooltipModule,
MatGoogleMapsAutocompleteModule,
AgmCoreModule,
],
declarations: [PdoProfileComponent], // this component uses agm-map
})
export class PdoProfileModule {}
custom-apikey-loader.service.ts (this will dynamically set the api per guide)
import { GoogleMapsScriptProtocol, LAZY_MAPS_API_CONFIG, LazyMapsAPILoaderConfigLiteral, MapsAPILoader } from '@agm/core';
import { DocumentRef, WindowRef } from '@agm/core/utils/browser-globals.d';
import { Inject, Injectable } from '@angular/core';
import { EnvService } from './env.service';
@Injectable()
export class CustomLazyAPIKeyLoader extends MapsAPILoader {
private _scriptLoadingPromise: Promise<any>;
private _config: LazyMapsAPILoaderConfigLiteral;
private _windowRef: WindowRef;
private _documentRef: DocumentRef;
private _key: string;
constructor(@Inject(LAZY_MAPS_API_CONFIG) config: any, w: WindowRef, d: DocumentRef, private envService: EnvService) {
super();
this._config = config || {};
this._windowRef = w;
this._documentRef = d;
//set google map api key
this.envService.getMapsKey().subscribe((res) => {
if (res.googleMapsApiKey) {
this._key = res.googleMapsApiKey;
}
})
}
load(): Promise<any> {
if (this._scriptLoadingPromise) {
return this._scriptLoadingPromise;
}
const script = this._documentRef.getNativeDocument().createElement('script');
script.type = 'text/javascript';
script.async = true;
script.defer = true;
const callbackName: string = `angular2GoogleMapsLazyMapsAPILoader`;
console.log('map key', this._key);
this._config.apiKey = this._key;
script.src = this._getScriptSrc(callbackName);
this._documentRef.getNativeDocument().body.appendChild(script);
this._scriptLoadingPromise = new Promise((resolve: Function, reject: Function) => {
(this._windowRef.getNativeWindow())[callbackName] = () => { console.log("loaded"); resolve(); };
script.onerror = (error: Event) => { reject(error); };
});
return this._scriptLoadingPromise;
}
//rest of the code is the same from https://gist.github.com/msaxena25/fa59c2130642de3ef6330c6cff2ce6c2
Upvotes: 0
Views: 373
Reputation: 945
Make your api-request before loading the app, and store the keys/data/user-profile somewhere synchronous. Then you will be able to access them synchronously.
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
async function preload(){
// Write your code here
// Get data from server and set it somewhere(cache/localstore).
}
preload().then(()=>{
bootstrapApplication(AppComponent, appConfig)
})
Alternatively, a dedicated angular module redirects clients to the corresponding pages based on user role, profile information, last-visited page, etc would be a good solution to such problems that require dynamic data. We can call this module a preflight module.
Upvotes: 1