DEV Community

Aaron K Saunders
Aaron K Saunders

Posted on

How to use the Ionic Capacitor Google Maps Plugin with Vue JS

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. We'll walk through the code step by step, discussing the code in the script section and the template section of the components used to build this web and mobile application

This application is built using Ionic Framework 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.

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";
Enter fullscreen mode Exit fullscreen mode

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.

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
];
Enter fullscreen mode Exit fullscreen mode

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.

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));
  }
};
Enter fullscreen mode Exit fullscreen mode

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

  <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>
Enter fullscreen mode Exit fullscreen mode

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.

import { onMounted, nextTick, ref, onUnmounted } from "vue";
import { GoogleMap } from "@capacitor/google-maps";
Enter fullscreen mode Exit fullscreen mode

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.

// PROPS
const props = defineProps<{
  markerData: { coordinate: any; title: string; snippet: string }[];
}>();
// EVENTS
const emits = defineEmits<{
  (event: "onMarkerClicked", info: any): void;
  (event: "onMapClicked"): void;
}>();
Enter fullscreen mode Exit fullscreen mode

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.

const mapRef = ref<HTMLElement>();
const markerIds = ref<string[] | undefined>();
let newMap: GoogleMap;
Enter fullscreen mode Exit fullscreen mode

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.

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[]);
});
Enter fullscreen mode Exit fullscreen mode

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.

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");
  });
}
Enter fullscreen mode Exit fullscreen mode

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.

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

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

<template>
  <ion-content >
    <p>{{JSON.stringify(marker,null,2)}}</p>
  </ion-content>
</template>
Enter fullscreen mode Exit fullscreen mode

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.

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

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

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

Enter fullscreen mode Exit fullscreen mode

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 logo aaronksaunders / ionic-vue-capacitor-google-maps-app-1

how to use ionic capacitor google map plugin in vue to build mobile app

Top comments (4)

Collapse
 
djbrady1 profile image
djbrady1

This was awesome and exactly what I was looking for! Thank you!

Collapse
 
aaronksaunders profile image
Aaron K Saunders

glad you found it helpful

Collapse
 
verza123 profile image
Angel Estrada

Image description

I'm still having issues with your code, and also do a simple integration as ionic tutorial mention. I think the problem is on the google maps key but I have with no restriction in order to test it, also create a new one with restriction on iOS app and the build name of the app. but no look, do you have any recommendation? thank you in advance

Collapse
 
aaronksaunders profile image
Aaron K Saunders

do you have downloaded the attached project and it doesn't run? did you follow the steps required when running in an emulator?