如何在 Angular 2 中的服务中加载 Google 地图

4

我按照官方文档中所述成功实现了Angular内置的Google Maps组件的延迟加载:https://github.com/angular/components/tree/master/src/google-maps

export class GoogleMapsDemoComponent {
    apiLoaded: Observable<boolean>;
    geoCoder: any;

    constructor(httpClient: HttpClient) {
        this.apiLoaded = httpClient.jsonp('https://maps.googleapis.com/maps/api/js?key=YOUR_KEY_HERE', 'callback')
        .pipe(
          map(() => true),
          catchError(() => of(false)),
        );
     }
    
    ngOnInit(): void {
        this.apiLoaded.subscribe(() => this.initGeoCoder());
    }
    
    initGeoCoder() {
        this.geoCoder = new google.maps.Geocoder();
    }
}

但是,如果组件被多次初始化,我现在会遇到错误。

You have included the Google Maps JavaScript API multiple times on this page. This may cause unexpected errors.

我想把加载Google脚本的任务放到一个专门的服务中,并将其注入到所有需要它的组件中。

Github仓库中的讨论确认了这种方法:

你应该可以将httpClient.jsonp('https://maps.googleapis.com/maps/api/js?key=YOUR_KEY_HERE','callback')和周围的API加载重构为一个服务,以便它可以充当单例并防止多个加载。请注意,您需要在@Injectable中添加providedIn:'root',以使它不会为每个模块创建实例,从而为每个实例加载API。

然而,我没能很好地完成这个任务,我一直收到以下错误信息,似乎是在Google脚本尚未加载时引发的:

ERROR ReferenceError: google is not defined

服务:

export class GeoService {
    apiLoaded: Observable<boolean>;

    constructor(private http: HttpClient) {

        this.apiLoaded = this.http
            .jsonp(
                'https://maps.googleapis.com/maps/api/js?key=API_KEY',
                'callback'
            )
            .pipe(
              map(() => true),
              catchError((error) => of(error))
            );
    }
}

组件:

export class GoogleMapsDemoComponent {
    apiLoaded: Observable<boolean>;
    geoCoder: any;

    constructor(geo: GeoService) {}
    
    ngOnInit(): void {
        this.geo.apiLoaded.subscribe(() => this.initGeoCoder());
    }
    
    initGeoCoder() {
        this.geoCoder = new google.maps.Geocoder();
    }
}

我的服务在一个共享模块中,它被导入到组件的模块中。我也尝试在同一模块内使用服务,以确保我没有弄错导入,但是我收到了相同的错误信息。

有人可以告诉我我漏掉了什么吗?


如果有人认为这个问题应该被踩,我真的很希望能得到一个小提示。我看不出这个问题怎么能改进。 - user3255061
你肯定需要npm安装正确的类型(@types/googlemaps),但这并不是阻碍你的全部。对于那些没有看到任何反应(并且没有记录jsonp调用的结果、成功或失败)的人,请确保在你的模块中导入了HttpClientModule和HttpClientJsonpModule。默默失败的方式,Angular! - Demetrios Christopher
2个回答

3
这是我的解决方案,不需要手动在DOM上创建元素。
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable, of } from "rxjs";
import { catchError, map, share, tap } from "rxjs/operators";

@Injectable({ providedIn: 'root' })
export class GoogleMapsApiService {

    private _loaded: boolean;
    private observable: Observable<boolean>;

    constructor(
        private http: HttpClient
    ) {
    }

    load(): Observable<boolean> {
        // if the api has already been loaded, return true to indicate it has been completed
        if (this._loaded) return of(this._loaded);

        // if a request to load (observable) is NOT currently outstanding, create the request (observable)
        if (!this.observable) {
            // append: your api key to this url here: ?key=XXXXX
            this.observable = this.http.jsonp('https://maps.googleapis.com/maps/api/js?key=xxxx', 'callback')
                .pipe(
                    map(() => true),
                    share(),
                    tap(() => {
                        this._loaded = true;
                        // clear the outstanding request
                        this.observable = undefined;
                    }),
                    catchError(err => {
                        console.error(err);
                        return of(false);
                    })
                );
        }

        // return the observable
        return this.observable;
    }
}

1

我最终采用了将Google Maps API加载到Angular2中的最佳方法是什么?https://github.com/angular/components/issues/21665的组合:

geo.service.ts:

geocode(
    request: google.maps.GeocoderRequest
 ): Observable<google.maps.GeocoderResult[]> {
    return new Observable<google.maps.GeocoderResult[]>((observer) => {
      if (!this.geocoder) {
        this.geocoder = new google.maps.Geocoder();
      }
      this.geocoder.geocode(request, (results, status) => {
        // need to manually trigger ngZone because "geocode" callback is not picked up properly by Angular
        this.ngZone.run(() => {
          if (status === google.maps.GeocoderStatus.OK) {
            // if status is "OK", the response contains a valid GeocoderResponse.
            observer.next(results);
            observer.complete();
          } else {
            observer.error(status);
          }
        });
      });
    });
  }

  loadGoogleMaps(url: string, id: string, callback): void {
    if (!document.getElementById(id)) {
      const script = document.createElement('script');
      script.type = 'text/javascript';
      script.src = url;
      script.id = id;
      if (callback) {
        script.addEventListener(
          'load',
          (e) => {
            callback(null, e);
          },
          false
        );
      }
      document.head.appendChild(script);
    }
  }

组件:

ngAfterViewInit() {
  this.geo.loadGoogleMaps(environment.googleMapsURL, 'google-map', () => {
    this.initGeoCoder();
});

geoDataFromAddress(
    address: google.maps.GeocoderRequest['address']
  ): Observable<google.maps.GeocoderResult> {
    return this.geo
      .geocode({ address: address.toLocaleLowerCase() })
      .pipe(map((results) => results[0]));
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接