Skip to content

Instantly share code, notes, and snippets.

@lancegliser
Created January 20, 2020 18:57
Show Gist options
  • Save lancegliser/70b9d51557e3271db37bd2d5486b5849 to your computer and use it in GitHub Desktop.
Save lancegliser/70b9d51557e3271db37bd2d5486b5849 to your computer and use it in GitHub Desktop.
Provides a single component for displaying a google map using minimum properties using typescript and Vue's composition API
<template>
<div
class="google-map"
ref="mapContainer"
:style="{ 'min-height': minHeight }"
/>
</template>
<script lang="ts">
import {
computed,
createComponent,
onMounted,
reactive,
ref,
watch
} from "@vue/composition-api";
import {
getGeocodeResults,
getGoogleMap,
getGeometryFromGeocodeResult
} from "../../../services/Google";
interface IProps {
mapConfig: google.maps.MapOptions;
minHeight: string;
address?: string;
}
const GoogleMap = createComponent<IProps>({
name: "GoogleMap",
props: {
mapConfig: {
type: Object,
default: () =>
({
clickableIcons: false,
streetViewControl: false,
backgroundColor: "$303030",
mapTypeControl: false,
zoomControlOptions: {
// style: google.maps.ZoomControlStyle.SMALL // Can not use because google is not defined at load
style: 1
},
zoom: 18,
mapTypeId: "satellite"
} as google.maps.MapOptions)
},
minHeight: {
type: String,
default: "300px"
},
address: String
},
setup(props: IProps) {
return { ...useState(props) };
}
});
export default GoogleMap;
interface IState {
isLoading: false;
map?: google.maps.Map;
marker?: google.maps.Marker;
}
function useState(props: IProps) {
const mapContainer = ref(null);
const state = reactive({
map: undefined,
isLoading: false
} as IState);
const geometry = computed(() => {
if (!props.address) {
return undefined;
}
return getGeocodeResults(props.address).then(getGeometryFromGeocodeResult);
});
onMounted(async () => {
const element: Element | undefined = mapContainer.value ?? undefined;
if (!element) {
return;
}
const map = await getGoogleMap();
state.map = new map(element, props.mapConfig);
});
watch([geometry, () => state.map], async () => {
if (!state.map) {
return;
}
// Remove any current marker
if (state.marker) {
state.marker.setMap(null);
}
const value = await geometry.value;
if (!value) {
return;
}
state.map.setCenter(value.location);
state.marker = new google.maps.Marker({
title: props.address,
position: value.location,
map: state.map
});
});
return { state, mapContainer };
}
</script>
<style scoped>
.google-map {
width: 100%;
}
</style>
import axios, { AxiosResponse } from "axios";
// Maps
import apiLoader from "load-google-maps-api";
export const GOOGLE_API_KEY = "XXXXXXX";
let googleMapApi: typeof google.maps;
export const getGoogleMapApi = async () => {
if (googleMapApi) {
return Promise.resolve(googleMapApi);
}
return apiLoader({
key: GOOGLE_API_KEY
}).then(instance => {
googleMapApi = instance;
return googleMapApi;
});
};
export const getGoogleMap = async (): Promise<typeof google.maps.Map> => {
const api = await getGoogleMapApi();
return api.Map;
};
export const getGeocodeResults = async (
address: string
): Promise<AxiosResponse<{ results: google.maps.GeocoderResult[] }>> => {
// We need to ensure the google apis have been loaded.
// This makes sure future functions have the correct data shapes available.
await getGoogleMapApi();
// TODO caching
return axios({
method: "GET",
url: `https://maps.googleapis.com/maps/api/geocode/json?address=${address}&key=${GOOGLE_API_KEY}`
});
};
export const getGeometryFromGeocodeResult = (
response: AxiosResponse<{ results: google.maps.GeocoderResult[] }>
): google.maps.GeocoderGeometry => {
const { lat, lng } = response.data.results[0].geometry.location;
// There's a bug in the TS description of this API. The returned result is LatLngLiteral, not the class version
// @ts-ignore
const latLng = new google.maps.LatLng(lat, lng);
return {
...response.data.results[0].geometry,
location: latLng
} as google.maps.GeocoderGeometry;
};
<template>
<GoogleMap :address="address" :min-height="minHeight" />
</template>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment