You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular中动态设置agm地图API密钥遇DI问题求助

问题解决:动态设置@agm/core API密钥并修复DI错误

错误原因分析

  1. 循环依赖:自定义的CustomLazyAPIKeyLoader继承自MapsAPILoader,同时MapsAPILoader的依赖链中又需要注入该自定义服务,导致Angular DI检测到循环依赖。
  2. 异步时机问题:在构造函数中直接订阅EnvService获取密钥,而load()方法可能在密钥返回前就执行,导致_key为空,同时构造函数中的异步操作干扰了DI初始化流程。
  3. 子模块重复导入: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 02:15:55