DEV Community

terrierscript
terrierscript

Posted on

Example for google map with vue.js without vue library

I want use google map in vue.js.
It's enable when use like vue2-google-map library, but I want more directry use google API.

Output

Demo: https://vue-google-map-provider-sample.netlify.com/
Source: https://github.com/inuscript/example-vue-inject-provide-google-map

Implements

I create those components.

  • index.html
  • MyMap.vue (Map parent. Combine some components)
  • MapLoader.vue (Only process Google Map callback)
  • MapProvider.vue (Only use vue.js provide
  • ChildMarker.vue (Example for marker child element)

index.html

    <!-- index.html -->
    <div id="app">
      <my-map :markers='[
        {"lat":35.6432027,"lng":139.6729435},
        {"lat":35.5279833,"lng":139.6989209},
        {"lat":35.6563623,"lng":139.7215211},
        {"lat":35.6167531,"lng":139.5469376},
        {"lat":35.6950961,"lng":139.5037899}
      ]'>
      </my-map>
    </div>

It is commonly index.html

MyMap.vue

Component parent example (not so important).

<!-- MyMap.vue -->
<template>
  <div>
    <h1>Map</h1>
    <map-loader 
      :map-config="mapConfig"
      apiKey="YOUR API KEY"
    >
      <template v-for="marker in markers">
        <child-marker :position="marker" />
      </template>
    </map-loader>
  </div>
</template>

<script>
import MapLoader from "./MapLoader.vue"
import ChildMarker from './ChildMarker'

export default {
  props: {
    markers: Array
  },
  data(){
    return {
      mapConfig: {
        zoom: 12,
        center: this.markers[0]
      }
    }
  },
  components: {
    MapLoader,
    ChildMarker
  }
}
</script>

This call map-loader, and use child-marker in internal.

MapLoader

This is the most core implementations (part 1).

<!-- MapLoader.vue -->
<template>
  <div>
    <div id="map"></div> <!-- point 1 -->
    <template v-if="!!this.google && !!this.map"> <!-- point 2 -->
      <map-provider
        :google="google"
        :map="map"
      >
        <slot/>
      </map-provider>
    </template>
  </div>
</template>

<script>
import GoogleMapsApiLoader from 'google-maps-api-loader'
import MapProvider from './MapProvider'

export default {
  props:{
    mapConfig: Object,
    apiKey: String
  },
  components: {
    MapProvider
  },
  data(){
    return {
      google: null,
      map: null
    }
  },
  mounted () { // point 3
    GoogleMapsApiLoader({
      apiKey: this.apiKey
    }).then((google) => {
      this.google = google
      this.initializeMap()
    })
  },
  methods: {
    initializeMap (){
      const mapContainer = this.$el.querySelector('#map') // point 1
      const { Map } = this.google.maps
      this.map = new Map(mapContainer, this.mapConfig)
    }
  }
}
</script>

<style scoped>
#map {
  height: 100vh;
  width: 100%;
}
</style>

This component some points.

  • point 1: This use #map and raw DOM. and after it mount with this.$el
  • point 2: Core of core. This suspend mount children until google or map value is valid. This make can use provide/inject.
  • point 3: Call GoogleMapApiLoader when mounted() lifecycle is called.

MapProvider

Core Implementation (part 2)
This setting provide and pass children to google and map value.

Vue's provide is evaluate only first time.
If set provide on map-loader, google and map value may null value and it's not update.

In this implementation, map-loader pass evaluated google and map value, map-provider can valid value.

<!-- MapProvider.vue -->
<template>
  <div>
    <slot />
  </div>
</template>

<script>
export default {
  props: {
    google: Object,
    map: Object,
  },
  provide() {
    return {
      google: this.google,
      map: this.map
    }
  },
}
</script>

ChildMarker

<!-- ChildMarker.vue -->
<template></template>
<script>
export default {
  inject: ["google", "map"],
  props: {
    position: Object
  },
  data(){
    return { marker: null}
  },
  mounted(){
    const { Marker } = this.google.maps
    this.marker = new Marker({
      position: this.position,
      map: this.map,
      title: "Child marker!"
    })
  }
}
</script>

Example for Marker child.
It's use inject and use mounted() same as MapLoader

Don't use provide/inject pattern (use slot-scope)

In official document, provide / inject is not recommended generic application.

If don't use provide / inject, we must pass values to children.
In this implementation, we need slot-scope too.

MapLoader remove MapProvider, and pass google and map to slot

<!-- MapLoader.vue -->
<template>
  <div>
    <div id="map"></div>
    <template v-if="!!this.google && !!this.map">
      <slot
        :google="google"
        :map="map"
      />
    </template>
  </div>
</template>

<script>
 :
</script>

Next, in MyApp, use slot-scope and pass value to child-marker

<!-- MyMap -->
<template>
  <div>
    <h1>Map</h1>
    <map-loader 
      :map-config="mapConfig"
      apiKey="YOUR API KEY">
      <template slot-scope="scopeProps"> <!-- slot-scope -->
        <child-marker 
          v-for="(marker,i) in markers"
          :key="i"
          :position="marker" 
          :google="scopeProps.google"
          :map="scopeProps.map"
        />
      </template>
    </map-loader>
  </div>
</template>

ChildMarker remove inject and got value with props.

<!-- ChildMarker -->
<template></template>
<script>
export default {
  props: {
    google: Object,
    map: Object,
    position: Object
  },
  data(){
    return { marker: null }
  },
  mounted(){
    const { Marker } = this.google.maps
    this.marker = new Marker({
      position: this.position,
      map: this.map,
      title: "Child marker!"
    })
  }
}
</script>

Top comments (1)

Collapse
 
gabalicious profile image
Justin Gaba

Thanks for this! I was wondering how to get Google Maps to play nice with ES6.