There are some libraries over there that we may install in our projects to use Google Maps, but considering the amount of KB's to add and that is another dependence that sometimes can be deprecated some day, I got in the position to create my own component, also I didn't going to use too many features.
The code I'm going to share can be useful to use the basics of Google Maps, or even to start your own component and give it more features that can be useful for your projects.
Obtaining the API Key
To use Maps JavaScript API, you'll need an API Key, which is a unique identifier used to authenticate the requests associated with your project for payment accounting purposes.
To get an API key, do the following:
Go to the GCP Console: https://cloud.google.com/console/google/maps-apis/overview
Use the drop-down menu to select or create the project that you want a new API Key for
Click the button to open the sidebar and go to API & Services > Credentials
On the Credentials page, click on Create Credentials > API key. The API Key Created dialog will show the new API Key.
You can read more about the Google Maps API Key in the documentation: https://developers.google.com/maps/documentation/javascript/get-api-key
Now create an environment file at the root of the project, called .env
, the content must be like
VUE_APP_MAPS_API_KEY="API_KEY"
Initializing the library
You can initialize the library with a function that returns a promise, which is resolved once the Google Maps library is downloaded in the browser and initialized.
Here is a basic example of how to create a map and add markers.
To actively use the Google Maps library in your Vue project, create a new script, /src/utils/gmaps.js
.
const API_KEY = process.env.VUE_APP_MAPS_API_KEY
const CALLBACK_NAME = 'initMap'
let initialized = !!window.google
let resolveInitPromise
let rejectInitPromise
// This promise handles the initialization
// status of the google maps script
const initPromise = new Promise((resolve, reject) => {
resolveInitPromise = resolve
rejectInitPromise = reject
})
export default function init() {
// if google maps is already init
// the `initPromise` should be resolved
if (initialized) return initPromise
initialized = true
// the callback function is called
// by the Google maps script if it is
// successfully loaded
window[CALLBACK_NAME] = () => resolveInitPromise(window.google)
// we inject a new tag into the Head
// of the HTML to load the Google Maps script
const script = document.createElement('script')
script.async = true
script.defer = true
script.src = `https://maps.googleapis.com/maps/api/js?key=${API_KEY}&callback=${CALLBACK_NAME}`
script.onerror = rejectInitPromise
document.querySelector('head').appendChild(script)
return initPromise
}
Creating a component to display the map
I'm gonna call the component GoogleMaps
and for so create the component at /src/components/GoogleMaps.vue
.
Let's add the markup at the template and the only CSS that's really mandatory
<template>
<div class="map" ref="map"></div>
</template>
<script></script>
<style scoped>
.map {
width: 100%;
height: 400px;
}
</style>
That's all the required code at the markup, we may add more in dependence of the layout of our application.
The mounted
lifecycle hook is asynchronous, and it waits for the Google Maps library to be initialized so that it can proceed to render the map using the drawMap
method, adding a marker to represent the user's location on the map.
<script>
import gmapsInit from '@/utils/gmaps'
import { isNumber, isArray } from 'util'
export default {
name: 'GoogleMaps',
data() {
return {
google: null,
map: null,
innerMarkers: [],
userMarker: null
}
},
async mounted() {
try {
// init and wait for the Google script is mounted
this.google = await gmapsInit()
// if the location is already set, for example
// when returning back to this view from another one
if ('lat' in this.myLocation && this.myLocation.lat) {
this.drawMap()
// set the current location
this.addMarker(this.myLocation)
}
} catch (err) {
console.log('ERROR:', err)
}
},
computed: {
myLocation() {
// if the coordinates is not set
if (!('lat' in this.center) && !isNumber(this.center.lat)) {
return null
}
// return the object expected by Google Maps
return { lat: this.center.lat, lng: this.center.lng }
}
},
methods: {
drawMap() {
if (this.myLocation.lat && this.myLocation.lng) {
// creating the map object, displaying it in the $el DOM object
this.map = new this.google.maps.Map(this.$refs['map'], {
zoom: 18,
center: this.myLocation
})
// center the canvas of the map to the location of the user
this.map.setCenter(this.myLocation)
}
},
}
}
</script>
We need to pass some data to the component, and I'm going to set as required prop
for the component is center
, which contains the coordinates for the center of the map and can receive a collection of markers via a prop with the same name.
<script>
props: {
center: {
type: Object,
required: true
},
markers: {
type: Array
}
},
</script>
Then the last thing is to add reactivity in front of the change of the position of the current location of the user, for so is also required to add some methods to work with the markers.
<script>
methods: {
// add a marker with a blue dot to indicate the user location
setUserMarker(location) {
this.userMarker = new this.google.maps.Marker({
position: location,
map: this.map
})
},
// Adds a marker to the map and push to the array
addMarker(location) {
// the marker positioned at `myLocation`
const marker = new this.google.maps.Marker({
position: location,
map: this.map
})
this.innerMarkers.push(marker)
},
// Sets the map on all markers in the array
setAllMarkersInMap(map) {
for (let i = 0; i < this.innerMarkers.length; i++) {
this.innerMarkers[i].setMap(map)
}
},
// Removes the markers from the map, but keeps them in the array
clearMarkers() {
this.setAllMarkersInMap(null)
},
// Deletes all markers in the array by removing references to them
deleteMarkers() {
this.clearMarkers()
this.innerMarkers = []
}
},
watch: {
marker: function(newVal) {
if (isArray(newVal)) {
// clear the markers
this.clearMarkers()
for (let i = 0; i < newVal.length; i++) {
let position = newVal[i]
if (
'lat' in position &&
isNumber(position.lat) &&
isNumber(position.lng)
) {
// set the current location
this.addMarker(position)
}
}
}
}
}
</script>
It is quite easy and now you can render maps in your application with a marker, with something like
<template>
<GoogleMaps :center="{ ...coordinates }" />
<template>
Placing all the code for the GoogleMap
component together
<template>
<div class="map" ref="map"></div>
</template>
<script>
import gmapsInit from '@/utils/gmaps'
import { isNumber, isArray } from 'util'
export default {
name: 'GoogleMaps',
props: {
center: {
type: Object,
required: true
},
markers: {
type: Array
}
},
async mounted() {
try {
// init and wait for the Google script is mounted
this.google = await gmapsInit()
// if the location is already set, for example
// when returning back to this view from another one
if ('lat' in this.myLocation && this.myLocation.lat) {
this.drawMap()
// set the current location
this.addMarker(this.myLocation)
}
} catch (err) {
console.log('ERROR:', err)
}
},
data() {
return {
google: null,
map: null,
innerMarkers: [],
userMarker: null
}
},
computed: {
myLocation() {
// if the coordinates is not set
if (!('lat' in this.center) && !isNumber(this.center.lat)) {
return null
}
// return the object expected by Google Maps
return { lat: this.center.lat, lng: this.center.lng }
}
},
methods: {
drawMap() {
if (this.myLocation.lat && this.myLocation.lng) {
// creating the map object, displaying it in the $el DOM object
this.map = new this.google.maps.Map(this.$refs['map'], {
zoom: 18,
center: this.myLocation
})
// center the canvas of the map to the location of the user
this.map.setCenter(this.myLocation)
}
},
// add a marker with a blue dot to indicate the user location
setUserMarker(location) {
this.userMarker = new this.google.maps.Marker({
position: location,
map: this.map
})
},
// Adds a marker to the map and push to the array
addMarker(location) {
// the marker positioned at `myLocation`
const marker = new this.google.maps.Marker({
position: location,
map: this.map
})
this.innerMarkers.push(marker)
},
// Sets the map on all markers in the array
setAllMarkersInMap(map) {
for (let i = 0; i < this.innerMarkers.length; i++) {
this.innerMarkers[i].setMap(map)
}
},
// Removes the markers from the map, but keeps them in the array
clearMarkers() {
this.setAllMarkersInMap(null)
},
// Deletes all markers in the array by removing references to them
deleteMarkers() {
this.clearMarkers()
this.innerMarkers = []
}
},
watch: {
marker: function(newVal) {
if (isArray(newVal)) {
// clear the markers
this.clearMarkers()
for (let i = 0; i < newVal.length; i++) {
let position = newVal[i]
if (
'lat' in position &&
isNumber(position.lat) &&
isNumber(position.lng)
) {
// set the current location
this.addMarker(position)
}
}
}
}
}
}
</script>
<style scoped>
.map {
width: 100%;
height: 400px;
}
</style>
Hoping it can be helpful for your project!
Top comments (0)