Angular中动态设置agm地图API密钥遇DI问题求助
问题解决:动态设置@agm/core API密钥并修复DI错误
错误原因分析
- 循环依赖:自定义的
CustomLazyAPIKeyLoader继承自MapsAPILoader,同时MapsAPILoader的依赖链中又需要注入该自定义服务,导致Angular DI检测到循环依赖。 - 异步时机问题:在构造函数中直接订阅
EnvService获取密钥,而load()方法可能在密钥返回前就执行,导致_key为空,同时构造函数中的异步操作干扰了DI初始化流程。 - 子模块重复导入:
PdoProfileModule中重复导入AgmCoreModule,可能导致DI提供者的冲突。
解决方案步骤
1. 修改自定义Loader的加载逻辑,确保密钥获取完成后再加载地图脚本
更新custom-apikey-loader.service.ts,将密钥获取逻辑移到load()方法中,等待密钥返回后再构建并加载Google Maps脚本:
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; constructor( @Inject(LAZY_MAPS_API_CONFIG) config: LazyMapsAPILoaderConfigLiteral, w: WindowRef, d: DocumentRef, private envService: EnvService ) { super(); this._config = { ...config }; // 复制基础配置 this._windowRef = w; this._documentRef = d; } load(): Promise<any> { if (this._scriptLoadingPromise) { return this._scriptLoadingPromise; } // 先获取API密钥,再加载脚本 this._scriptLoadingPromise = this.envService.getMapsKey().toPromise() .then(res => { if (res.googleMapsApiKey) { this._config.apiKey = res.googleMapsApiKey; } // 构建并加载地图脚本 const script = this._documentRef.getNativeDocument().createElement('script'); script.type = 'text/javascript'; script.async = true; script.defer = true; const callbackName: string = `angular2GoogleMapsLazyMapsAPILoader`; script.src = this._getScriptSrc(callbackName); this._documentRef.getNativeDocument().body.appendChild(script); return new Promise((resolve, reject) => { (this._windowRef.getNativeWindow())[callbackName] = () => { console.log("地图脚本加载完成"); resolve(); }; script.onerror = (error: Event) => reject(error); }); }) .catch(err => { console.error("获取地图API密钥失败", err); throw err; }); return this._scriptLoadingPromise; } // 保留原有的_getScriptSrc方法 private _getScriptSrc(callbackName: string): string { let protocol: GoogleMapsScriptProtocol = this._config.protocol || 'https'; if (protocol === 'auto') { protocol = this._windowRef.getNativeWindow().location.protocol === 'https:' ? 'https' : 'http'; } const hostAndPath: string = this._config.apiUrl || 'maps.googleapis.com/maps/api/js'; return `${protocol}://${hostAndPath}?callback=${callbackName}&key=${this._config.apiKey}&libraries=${this._config.libraries?.join(',') || ''}`; } }
2. 修复AppModule的DI配置,避免循环依赖
将MapsAPILoader的提供者改为工厂函数,而非直接使用useClass,绕过Angular的循环依赖检测:
@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 }, // 用工厂函数创建CustomLazyAPIKeyLoader,避免循环依赖 { provide: MapsAPILoader, useFactory: (config: LazyMapsAPILoaderConfigLiteral, windowRef: WindowRef, docRef: DocumentRef, envService: EnvService) => { return new CustomLazyAPIKeyLoader(config, windowRef, docRef, envService); }, deps: [LAZY_MAPS_API_CONFIG, WindowRef, DocumentRef, EnvService] } ], bootstrap: [AppComponent], }) export class AppModule {}
3. 清理子模块的重复导入
修改PdoProfileModule,移除AgmCoreModule的导入(AppModule已通过forRoot()全局配置AGM,子模块无需重复导入):
@NgModule({ imports: [ CommonModule, RouterModule.forChild(PdoProfileRoutes), FormsModule, SharedModule, MatTooltipModule, MatGoogleMapsAutocompleteModule // 移除AgmCoreModule导入 ], declarations: [PdoProfileComponent], }) export class PdoProfileModule {}
关键说明
- 使用
toPromise()将Observable转为Promise,确保密钥获取完成后再加载地图脚本,避免_key为空的问题。 - 工厂函数方式创建Loader,打破了DI的循环依赖链,解决
NG0200错误。 - 子模块不再重复导入
AgmCoreModule,确保全局配置的唯一性,避免DI冲突。
内容的提问来源于stack exchange,提问作者lance2k
相关产品推荐
相关产品推荐

