How to use the Ionic Capacitor Google Maps Plugin with Vue JS
Tags: vue, googlemaps, ionic, mobile
Published: 2023-06-20 19:34:10

In this blog post, we'll explore how to build a Vue.js mobile application that integrates with Google Maps using the [Ionic Capacitor Google Map Plugin](https://capacitorjs.com/docs/apis/google-maps). We'll walk through the code step by step, discussing the [code](https://github.com/aaronksaunders/ionic-vue-capacitor-google-maps-app-1) in the script section and the template section of the components used to build this web and mobile application

{% youtube nuVS6nXzRgE %}

This application is built using [Ionic Framework](https://ionicframework.com/docs/vue/overview) and Vue JS. We are only using Ionic Framework for the UI components, and those components can be replaced with any vue js compatible UI components

### HomePage.vue
#### Script Section:
**Imports**:
We start by importing the necessary dependencies and components. We import Ionic framework components from the `@ionic/vue` package, and we use the vue package to access the ref function, which enables us to create reactive references. Additionally, we import two custom components, `MyMap` and `MarkerInfoWindow`, from other files. Lastly, we import `Capacitor` from the Capacitor framework, which is used for building cross-platform apps.
```javascript
import {
  IonContent,
  IonHeader,
  IonPage,
  IonTitle,
  IonToolbar,
  IonPopover,
  modalController,
} from "@ionic/vue";
import { ref } from "vue";
import MyMap from "./MyMap.vue";
import MarkerInfoWindow from "../components/MarkerInfoWindow.vue";
import { Capacitor } from "@capacitor/core";
```

**Variable Declarations**:
We declare two reactive reference variables: `markerInfo` and `markerIsOpen`. These variables will hold information about the currently selected marker and whether the marker popover is open or closed, respectively.

We define an array called `markerData` that holds sample data for markers on the map. Each marker object consists of `coordinates` (latitude and longitude), a `title`, and a `snippet`. You can customize this data by adding more markers as needed.

```javascript
const markerInfo = ref<any>();
const markerIsOpen = ref<boolean>(false);

// sample data for the map
const markerData = [
  {
    coordinate: { lat: 37.769, lng: -122.446 },
    title: "title one",
    snippet: "title one snippet content will be presented here",
  },
  {
    coordinate: { lat: 37.774, lng: -122.434 },
    title: "title two",
    snippet: "title two snippet content will be presented here",
  },
  {
    coordinate: { lat: 37.783, lng: -122.408 },
    title: "title three",
    snippet: "title three snippet content will be presented here",
  },
  // Add more points as needed
];
```

**Functions**:

`openModal` is an event handler for when a marker is clicked. It creates and presents a modal window using `modalController.create()`. The modal displays the `MarkerInfoWindow` component, which shows detailed information about the `marker`.

`mapClicked` is an event listener for when the map is clicked. It simply logs a message to the console, allowing you to perform any desired actions when the map is clicked.

`getMarkerInfo` takes a `marker` object as input and returns the associated information from the `markerData` array. This function is used to retrieve the marker information when a marker is clicked.

`markerClicked` is an event handler for when a marker is clicked. It checks if the platform is not native (i.e., running on the web) using `Capacitor.isNativePlatform()`. If it is not native, it calls the `openModal` function to display the marker information in a modal. This distinction is necessary because the web version doesn't show an info window directly on the map.

```javascript
const openModal = async (marker: any) => {
  const modal = await modalController.create({
    component: MarkerInfoWindow,
    componentProps: {
      marker,
    },
    initialBreakpoint: 0.2,
    breakpoints: [0, 0.2],
    backdropBreakpoint: 0,
    showBackdrop: false,
    backdropDismiss: true,
  });

  modal.present();

  const { data, role } = await modal.onWillDismiss();
};


const mapClicked = () => {
  console.log("mapClicked");
};


const getMarkerInfo = (marker: { latitude: number; longitude: number }) => {
  return markerData.filter(
    (m) =>
      m.coordinate.lat === marker.latitude &&
      m.coordinate.lng === marker.longitude
  )[0];
};


const markerClicked = (event: any) => {
  console.log(event);


  // only use dialog in web since we doesnt show info window
  if (!Capacitor.isNativePlatform()) {
    openModal(getMarkerInfo(event));
  }
};
```

#### Template Section:
The template section defines the structure and layout of our component's HTML template using Ionic Vue components. Let's go through it:

`<ion-page>` represents a page within our Ionic app. It provides a container for the entire content.

`<ion-header>` contains the header toolbar for the page. We use it to display the app title.

`<ion-toolbar>` represents a toolbar within the header. It provides a place for buttons, titles, and other elements.

`<ion-title>` displays the title of the page. In this case, it's set to "Vue + Capacitor + Google Maps".

`<ion-content>` contains the main content of the page. It provides a scrollable area where we'll display our map and markers.

`<my-map>` is a custom component that represents the map. We pass in the `markerData` prop to provide the marker information. The component also emits two events: `onMapClicked` and `onMarkerClicked`, which allow us to handle map and marker clicks, respectively.

`<ion-popover>` represents a popover, which is a small overlay window. It takes the `isOpen` prop to control the popover's visible status

```html
  <ion-page>
    <ion-header :translucent="true">
      <ion-toolbar>
        <ion-title>Vue + Capacitor + Google Maps</ion-title>
      </ion-toolbar>
    </ion-header>

    <ion-content>
      <my-map
        :markerData="markerData"
        @onMapClicked="mapClicked"
        @onMarkerClicked="markerClicked"
      ></my-map>
      <ion-popover
        :is-open="markerIsOpen"
        size="cover"
        @did-dismiss="markerIsOpen = false"
      >
        <ion-content class="ion-padding">
          <div>{{ markerInfo?.title }}</div>
        </ion-content>
      </ion-popover>
    </ion-content>
  </ion-page>
```

### MyMap.vue

In this section, we'll discuss a new component responsible for rendering the map and markers using the Capacitor Google Maps plugin. We'll explain the script section, which contains the logic, and the template section, which defines the component's structure.

#### Script Section:
The script section contains the logic and functionality of the custom map component. Let's break it down:

**Imports**
We import necessary functions from the vue package, such as onMounted, nextTick, ref, and onUnmounted. These functions are used to handle component lifecycle events. We also import the GoogleMap object from the @capacitor/google-maps package, which provides the functionality to interact with the Google Maps plugin.
```javascript
import { onMounted, nextTick, ref, onUnmounted } from "vue";
import { GoogleMap } from "@capacitor/google-maps";
```

**Props and Emits Declarations**
The component defines two parts: props and emits. Props allow us to pass data into the component, and emits enable us to trigger custom events. 

In this case, the component expects a prop named `markerData` that contains an array of marker objects. 
The emits section declares two events: `onMarkerClicked` and `onMapClicked`. These events will be emitted back to the parent component when a marker or the map is clicked.
```javascript
// PROPS
const props = defineProps<{
  markerData: { coordinate: any; title: string; snippet: string }[];
}>();
// EVENTS
const emits = defineEmits<{
  (event: "onMarkerClicked", info: any): void;
  (event: "onMapClicked"): void;
}>();
```

**Variable Declarations**

`mapRef` is a reactive reference variable that holds a reference to the map element in the DOM.
`markerIds` is a reactive reference variable that holds an array of marker IDs.
`newMap` is a variable that will hold an instance of the GoogleMap object.
```javascript
const mapRef = ref<HTMLElement>();
const markerIds = ref<string[] | undefined>();
let newMap: GoogleMap;
```

**Lifecycle Hooks**

`onMounted` is a lifecycle hook that runs when the component is mounted to the DOM. Inside this hook, we use `nextTick` to wait for the component to render completely. Then, we call the `createMap` function to initialize and render the map.

`onUnmounted` is a lifecycle hook that runs when the component is about to be unmounted. Inside this hook, we call the `removeMarkers` method of the `newMap` object to remove the markers from the map.
```javascript
onMounted(async () => {
  console.log("mounted ", mapRef.value);
  await nextTick();
  await createMap();
});

// remove markers on unmount
onUnmounted(() => {
  console.log("onunmounted");
  newMap.removeMarkers(markerIds?.value as string[]);
});
```

**Function Declarations**

`addSomeMarkers` is an async function that takes an instance of the GoogleMap object as a parameter. It removes any existing markers from the map and adds new markers based on the markerData prop passed to the component.

`createMap` is an async function responsible for creating and rendering the map. It first checks if the `mapRef` value exists. Then, it uses the Capacitor Google Maps plugin to create a new map instance. It sets the map's ID, the associated DOM element, the API key, and the initial configuration (center and zoom level). 
After creating the map, it calls the `addSomeMarkers` function to add the markers to the map. Lastly, it sets event listeners to handle marker clicks and map clicks, emitting the appropriate events to the parent component.

```javascript
const addSomeMarkers = async (newMap: GoogleMap) => {
  markerIds?.value && newMap.removeMarkers(markerIds?.value as string[]);

  // Plot each point on the map
  let markers = props.markerData.map(({ coordinate, title, snippet }) => {
    return {
      coordinate,
      title,
      snippet,
    };
  });

  markerIds.value = await newMap.addMarkers(markers);
};

/**
 * 
 */
async function createMap() {
  if (!mapRef.value) return;

  // render map using capacitor plugin
  newMap = await GoogleMap.create({
    id: "my-cool-map",
    element: mapRef.value,
    apiKey: import.meta.env.VITE_APP_YOUR_API_KEY_HERE as string,
    config: {
      center: {
        lat: 37.783,
        lng: -122.408,
      },
      zoom: 12,
    },
  });

  // add markers to map
  addSomeMarkers(newMap);

  // Set Event Listeners...
  // Handle marker click, send event back to parent
  newMap.setOnMarkerClickListener((event) => {
    emits("onMarkerClicked", event);
  });

  // Handle map click, send event back to parent
  newMap.setOnMapClickListener(() => {
    emits("onMapClicked");
  });
}
```

#### Template Section
The template section defines the structure of the component's HTML template. Here's a breakdown:

`<div>`: Acts as a container for the map component.

`<capacitor-google-map>`: Represents the custom map component provided by the Capacitor Google Maps plugin. It accepts a ref attribute (mapRef) to reference the map element and applies inline styles to set the display, width, and height of the map.

```html
<template>
  <div>
    <capacitor-google-map
      ref="mapRef"
      style="display: inline-block; width: 100vw; height: 86vh"
    >
    </capacitor-google-map>
  </div>
</template>
```

In this section, we explored the code for a custom map component that integrates with Capacitor and Google Maps. The script section handles the logic, including the creation of the map, adding markers, and handling events. The template section defines the structure of the component's HTML template, which includes a `<div>` container and the `<capacitor-google-map>` element provided by the Capacitor Google Maps plugin.


### MarkerInfoWindow.vue

In this section, we'll discuss a Vue component that displays marker information. We are using this component in this example for only the Web version of the application since the expected GoogleMaps `InfoWindow` is not supported.

We'll explain the template section, which renders the marker data, and the script section, which defines the component's logic.

#### Template Section:
The template section is responsible for rendering the component's HTML structure. Let's break it down:

`<ion-content>`:
This Ionic framework component represents the main content area of the component. It acts as a container for the component's content.

`<p>`:
This HTML paragraph element displays the marker information. The content of the paragraph is generated dynamically using the Vue expression `JSON.stringify(marker,null,2)`. It uses the `JSON.stringify()` function to convert the marker prop into a formatted JSON string with an indentation of 2 spaces.

This was just a placeholder for information that you would want to formation and display for the user to see what marker was just clicked

```vue
<template>
  <ion-content >
    <p>{{JSON.stringify(marker,null,2)}}</p>
  </ion-content>
</template>
```

#### Script Section:
The script section contains the logic and functionality of the component. Let's go through it:

**Imports:**
We import the all the Ionic Component from the @ionic/vue package that are needed for this component. This component is used in the template section to represent the content area.

**Props and Emits Declarations:**
The component defines two parts: `props` and `emits`. Props allow us to pass data into the component, and emits enable us to trigger custom events. 

In this case, the component expects a prop named `marker` that contains an object with `coordinate` and `title` properties. 

The emits section declares an event named `onDismiss`.

```javascript
<script setup lang="ts">
import {
  IonContent,
} from "@ionic/vue";

// PROPS
const props = defineProps<{
  marker: { coordinate: any; title: string };
}>();

const emits = defineEmits<{
  (event: "onDismiss"): void;
}>();

```

Summary:
In this section, we examined the code for a Vue component that displays marker information. The template section renders the marker data using the `JSON.stringify()` expression. The script section defines the props and emits declarations.

You can use this component to display the marker information in your Vue application. Customize the template section as needed to achieve the desired layout and styling.

### Conclusion:
In this blog post, we examined the code for a custom map component built with Vue, Ionic Capacitor, and Google Maps. 

By understanding this code, you now have a foundation for building a Vue mobile application that integrates with Ionic Capacitor and Google Maps. You can customize the component further by adding additional functionality or styling to suit your specific needs.

Remember to properly configure your API key in the `createMap` function to ensure that the Google Maps plugin functions correctly. Additionally, feel free to explore the Ionic Capacitor and Google Maps documentation for more advanced features and capabilities.

{% github aaronksaunders/ionic-vue-capacitor-google-maps-app-1 %}