Search code examples
angulargoogle-mapslazy-loading

Angular - Lazy Loading google maps - "You have included the Google Maps JavaScript API multiple..."


I am trying to use @angular/google-maps.

All is working, but I keep getting the following error on the browser:

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

I am trying to Lazy Loading the API.

"@angular/animations": "~13.3.0",
"@angular/common": "~13.3.0",
"@angular/compiler": "~13.3.0",
"@angular/core": "~13.3.0",
"@angular/forms": "~13.3.0",
"@angular/google-maps": "^13.3.9",

I have a shared map component, that is used by other components.

So in this component, I tried what the documentation says (documentation):

export class MapComponent {
  apiLoaded: Observable<boolean>;

  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)),
        );
  }

 ...
}

this approach is causing the error. Each time this component is used the constructor is called and well the API is loaded again.

So I tried to do this on a singleton service, like this:

@Injectable({
  providedIn: 'root'
})
export class MapServiceService {
  apiLoaded!: Observable<boolean>;

  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)),
    );
  }

  isApiLoaded(): Observable<boolean> {
    return this.apiLoaded;
  }
}

And then on the component I am doing this:

export class MapComponent implements OnInit {

  options!: google.maps.MapOptions;
  apiLoaded!: Observable<boolean>;

  constructor(private _mapService: MapServiceService) {
  }
  
  ngOnInit(): void {
    this.apiLoaded = this._mapService.isApiLoaded();
  }
...

I can see that now the service's constructor is called once, but I am still getting that error.

What would be the correct approach for this?

Thanks for reading!


Solution

  • The issue is with calling apiLoaded which is observable.

    In the UI I was doing the following:

        <div *ngIf="apiLoaded | async">
            <google-map height="500px" width="100%" [options]="options"></google-map>
        </div>
    

    Basically, I am subscribing to the isApiLoaded() method that ends up trying to load the API each time I render the component.

    This is my solution:

      export class MapServiceService {
    
      private currentApiStatus: BehaviorSubject<Boolean>;
      obsCurrentApiStatus: Observable<Boolean>;
    
      constructor(httpClient: HttpClient) {
        this.currentApiStatus =  new BehaviorSubject(new Boolean(false));
        this.obsCurrentApiStatus = this.currentApiStatus.asObservable();
    
        httpClient.jsonp('https://maps.googleapis.com/maps/api/js?key=YOUR_KEY_HERE', 'callback')
        .pipe(
          map(() => true),
          catchError(() => of(false)),
        ).subscribe( loaded => {
          this.currentApiStatus.next(loaded);
        });
      }
    }
    

    then on the component:

      apiLoaded!: boolean;
    
      constructor(private _mapService: MapServiceService) {
      }
      
      ngOnInit(): void {
      
        this._mapService.obsCurrentApiStatus.subscribe(status => {
          this.apiLoaded = status.valueOf();
        });
      ...
    

    and in the UI:

        <div *ngIf="apiLoaded">
            <google-map height="500px" width="100%" [options]="options"></google-map>
        </div>
    

    I hope this saves time for somebody and if you know a better way to implement this I am open to learning!

    Thanks!