Adding Google Maps to the Angular or Ionic application (SDK)

Of course, the easiest way to add Google Maps to any Angular application is by using the @agm/core library. In some cases, using the SDK can also be beneficial. The script will only load once when you open the page with Google Maps.

Creating map

HTML template (modal window)

<ion-header>
  <ion-toolbar>
    <ion-title>Pick Location</ion-title>
    <ion-buttons slot="primary">
      <ion-button (click)="onCancel()">Cancel</ion-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<ion-content>
  <div #map class="map"></div>
</ion-content>

CSS file

.map {
  position: absolute;
  height: 100%;
  width: 100%;

  background-color: transparent;
  opacity: 0;
  transition: opacity 150ms ease-in;

  &.visible {
    opacity: 1;
  }
}

The map will initially be invisible. Once its loading is complete, we need to add the ‘visible’ class with a simple animation.

Ts controller

import {AfterViewInit, Component, ElementRef, OnInit, Renderer2, ViewChild} from '@angular/core';
import {ModalController} from "@ionic/angular";

@Component({
  selector: 'app-map-modal',
  templateUrl: './map-modal.component.html',
  styleUrls: ['./map-modal.component.scss'],
})
export class MapModalComponent implements AfterViewInit {
  @ViewChild('map') mapElRef!: ElementRef;

  constructor(
    private modalCtrl: ModalController,
    private renderer: Renderer2
  ) {
  }

  ngAfterViewInit() {
    this.getGoogleMap().then((googleMaps) => {
      const mapEl = this.mapElRef.nativeElement;
      const map = new googleMaps.Map(mapEl, {
        center: {lat: 49.40328, lng: 33.21939},
        zoom: 10
      });
      googleMaps.event.addListenerOnce(map, 'idle', () => {
        this.renderer.addClass(mapEl, 'visible');
      });
    }).catch(err => {
      console.error(err);
    });
  }

  onCancel() {
    this.modalCtrl.dismiss();
  }

  private getGoogleMap(): Promise<any> {
    const win = window as any;
    const googleModule = win.google;
    if (googleModule && googleModule.maps) {
      return Promise.resolve(googleModule.maps);
    }
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = 'https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY';
      script.async = true;
      script.defer = true;
      document.body.appendChild(script);
      script.onload = () => {
        const loadedGoogleModule = win.google;
        if (loadedGoogleModule && loadedGoogleModule.maps) {
          resolve(loadedGoogleModule.maps);
        } else {
          reject('Google maps SDK not available');
        }
      }
    });
  }
}

In the getGoogleMap method, we first check if the googleModule is already available. If it is, we return a promise with it. Otherwise, we create and return a promise that creates the script and appends it to the body. We then resolve the promise using the same logic as in the part of the code where the googleModule was available immediately.

We execute this method in ngAfterViewInit and listen for events when the map is created to add a visible class to our div block with the map. This adds a visual enhancement as it may take a few milliseconds for the map to load, and with animation, it will appear more seamless.

Picking Locations via a Click on the Map

To capture coordinates when clicking, we must add another listener. However, this time it will be a listener for an instance of the map object named map in our code, rather than the global object googleMaps as before.

private clickListener: any;

ngAfterViewInit() {
  this.getGoogleMap().then((googleMaps) => {
    const mapEl = this.mapElRef.nativeElement;
    const map = new googleMaps.Map(mapEl, {
      center: {lat: 49.40328, lng: 33.21939},
      zoom: 10
    });
    googleMaps.event.addListenerOnce(map, 'idle', () => {
      this.renderer.addClass(mapEl, 'visible');
    });

    this.clickListener = map.addListener('click', (event: any) => {
      const selectedCoords = {
        lat: event.latLng.lat(),
        lng: event.latLng.lng()
      };
      console.log(selectedCoords);
      this.modalCtrl.dismiss(selectedCoords);
    })
  }).catch(err => {
    console.error(err);
  });
}

ngOnDestroy() { 
  if (this.clickListener) { 
    this.clickListener.remove(); 
  } 
}

Geocoding (transform coordinates to address)

To convert coordinates to address, you can use this function:

  private getAddress(lat: number, lng: number) {
    return this.http
      .get<any>(`https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&key=${environment.googleMapsAPIKet}`)
      .pipe(map((geoData) => {
        if (!geoData?.results?.length) {
          return null;
        } else {
          return geoData.results[0].formatted_address;
        }
      }));
  }
}

Geocoding API.

Actually, the array results may contain multiple addresses. The example provided takes only the first one in the map pipe.

Static image from Google Maps

Google Maps also provides the option to use a static image instead of a dynamic map. To do this, you only need to use the correct link with coordinates and your own API key.

private getMapImage(lat: number, lng: number, zoom: number) {
  return `https://maps.googleapis.com/maps/api/staticmap?center=${lat},${lng}&zoom=${zoom}&size=400x400&markers=color:red%7Clabel:Place:%7C${lat},${lng}&key=${environment.googleMapsAPIKet}`;
}

And we can add this function inside the method for working with a modal window:

onPickLocation() {
  this.modalCtrl.create({component: MapModalComponent}).then(modalEl => {
      modalEl.onDidDismiss().then((modalData => {
        if (modalData.data) {
          const pickedLocation: PlaceLocation = {
            lat: modalData.data.lat,
            lng: modalData.data.lng,
            address: null,
            staticMapImageUrl: null
          };

          this.getAddress(modalData.data.lat, modalData.data.lng)
            .pipe(switchMap((address) => {
              pickedLocation.address = address;
              return of(this.getMapImage(pickedLocation.lat, pickedLocation.lng, 12))

            }))
            .subscribe((staticMapImageUrl: any) => {
              pickedLocation.staticMapImageUrl = staticMapImageUrl;
              this.selectedLocationImage = staticMapImageUrl;
            });
        }
      }))
      modalEl.present();
    }
  )
}

And finally, use this link for the image source in the HTML template:

<div class="picker">
  @if (selectedLocationImage) {
    <ion-img class="location-img" [src]="selectedLocationImage"></ion-img>
  } @else {
    <ion-button color="primary" (click)="onPickLocation()">
      <ion-icon name="map" slot="start"></ion-icon>
      <ion-label>Select location</ion-label>
    </ion-button>
  }
</div>
.picker {
  width: 30rem;
  max-width: 80%;
  height: 20rem;
  max-height: 30vh;
  border: 1px solid var(--ion-color-primary);
  margin: auto;
  display: flex;
  justify-content: center;
  align-items: center;

  .location-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
}

Maps Static API

Loading map in view mode (non-selectable) with marker

All the examples provided previously were for selecting a point on the map. However, now we will make our component (modal) more versatile by adding functionality to display a previously selected point. For instance, this could be done after clicking on a static map image.

To achieve this, we will add a few Inputs and logic to add a marker to the map when it’s in non-selectable mode.

  @Input() center = {lat: 49.40328, lng: 33.21939};
  @Input() selectable = true;

...

 ngAfterViewInit() {
    this.getGoogleMap().then((googleMaps) => {
      const mapEl = this.mapElRef.nativeElement;
      const map = new googleMaps.Map(mapEl, {
        center: this.center,
        zoom: 10
      });
      googleMaps.event.addListenerOnce(map, 'idle', () => {
        this.renderer.addClass(mapEl, 'visible');
      });

      if (this.selectable) {
        this.clickListener = map.addListener('click', (event: any) => {
          const selectedCoords = {
            lat: event.latLng.lat(),
            lng: event.latLng.lng()
          };
          this.modalCtrl.dismiss(selectedCoords);
        });
      } else {
        const marker = new googleMaps.Marker({
          position: this.center,
          map,
          title: 'Picked Location'
        });
        marker.setMap(map);
      }
    }).catch(err => {
      console.error(err);
    });
  }

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *