Vue Integration Guide
Learn how to integrate route optimization and mapping into your Vue 3 applications using @route-optimization/vue.
Installation
bash
npm install @route-optimization/vue @route-optimization/core
# or
pnpm add @route-optimization/vue @route-optimization/core
# or
yarn add @route-optimization/vue @route-optimization/coreCore Concepts
The Vue package provides composables that integrate with Vue 3's Composition API:
useRouteMap- Main composable for map rendering and route managementuseMapControls- Composable for map interaction controlsuseRouteOptimization- Composable for route calculation and optimizationuseOptimizationStatus- Composable for optimization progress tracking
Basic Map Rendering
Simple Route Map
vue
<script setup lang="ts">
import { ref } from 'vue';
import { useRouteMap } from '@route-optimization/vue';
const mapRef = ref<HTMLDivElement>();
const { renderRoute, clearRoute, isReady } = useRouteMap({
apiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
center: { lat: 13.7563, lng: 100.5018 }, // Bangkok
zoom: 12,
mapElement: mapRef,
});
const handleShowRoute = () => {
renderRoute({
stops: [
{
id: '1',
location: { lat: 13.7563, lng: 100.5018 },
name: 'Start Location',
},
{
id: '2',
location: { lat: 13.7467, lng: 100.5352 },
name: 'End Location',
},
],
});
};
</script>
<template>
<div>
<div ref="mapRef" style="width: 100%; height: 600px" />
<button @click="handleShowRoute" :disabled="!isReady">Show Route</button>
<button @click="clearRoute" :disabled="!isReady">Clear Route</button>
</div>
</template>Route Map View Component
For simpler usage, use the pre-built RouteMapView component:
vue
<script setup lang="ts">
import { ref } from 'vue';
import { RouteMapView } from '@route-optimization/vue';
import type { Route } from '@route-optimization/core';
const route = ref<Route | null>(null);
const handleError = (error: Error) => {
console.error('Map error:', error);
};
</script>
<template>
<RouteMapView
api-key="YOUR_GOOGLE_MAPS_API_KEY"
:route="route"
:map-config="{
center: { lat: 13.7563, lng: 100.5018 },
zoom: 12,
}"
@error="handleError"
/>
</template>Map Controls
Using Map Controls Composable
vue
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useRouteMap, useMapControls } from '@route-optimization/vue';
const mapRef = ref<HTMLDivElement>();
const { map, isReady } = useRouteMap({
apiKey: 'YOUR_API_KEY',
mapElement: mapRef,
});
const { center, zoom, bounds, setCenter, setZoom, fitBounds } = useMapControls(map);
const handleCenterBangkok = () => {
setCenter({ lat: 13.7563, lng: 100.5018 });
};
const handleZoomIn = () => {
setZoom((zoom.value ?? 12) + 1);
};
const currentCenter = computed(() => {
if (!center.value) return 'Loading...';
return `${center.value.lat.toFixed(4)}, ${center.value.lng.toFixed(4)}`;
});
</script>
<template>
<div>
<div ref="mapRef" style="width: 100%; height: 600px" />
<div class="controls">
<p>Center: {{ currentCenter }}</p>
<p>Zoom: {{ zoom }}</p>
<button @click="handleCenterBangkok">Center on Bangkok</button>
<button @click="handleZoomIn">Zoom In</button>
</div>
</div>
</template>Route Optimization
Basic Optimization
vue
<script setup lang="ts">
import { useRouteOptimization } from '@route-optimization/vue';
import type { OptimizationRequest } from '@route-optimization/core';
const { status, data, error, isLoading, isSuccess, optimize, reset } = useRouteOptimization({
useMockMode: true, // Use mock mode for development
});
const handleOptimize = async () => {
const request: OptimizationRequest = {
shipments: [
{
id: 'shipment-1',
deliveryLocation: { latitude: 13.7563, longitude: 100.5018 },
},
{
id: 'shipment-2',
deliveryLocation: { latitude: 13.7467, longitude: 100.5352 },
},
],
vehicles: [
{
id: 'vehicle-1',
startLocation: { latitude: 13.7563, longitude: 100.5018 },
},
],
};
try {
const response = await optimize(request);
console.log('Optimization result:', response);
} catch (err) {
console.error('Optimization failed:', err);
}
};
</script>
<template>
<div>
<button @click="handleOptimize" :disabled="isLoading">
{{ isLoading ? 'Optimizing...' : 'Optimize Routes' }}
</button>
<div v-if="isSuccess && data" class="result">
<h3>Optimization Complete!</h3>
<p>Routes: {{ data.routes?.length ?? 0 }}</p>
<p>Total Distance: {{ data.metrics?.totalDistance ?? 0 }} km</p>
</div>
<div v-if="error" class="error">Error: {{ error }}</div>
</div>
</template>Production Mode with Google Cloud
vue
<script setup lang="ts">
import { useRouteOptimization } from '@route-optimization/vue';
const { optimize } = useRouteOptimization({
useMockMode: false,
projectId: 'your-google-cloud-project',
credentials: {
client_email: 'service-account@project.iam.gserviceaccount.com',
private_key: import.meta.env.VITE_GOOGLE_PRIVATE_KEY,
},
});
</script>Progress Tracking
vue
<script setup lang="ts">
import { useRouteOptimization, useOptimizationStatus } from '@route-optimization/vue';
const { optimize } = useRouteOptimization({ useMockMode: true });
const {
progress,
step,
setProgress,
reset: resetProgress,
} = useOptimizationStatus({
onStart: () => console.log('Optimization started'),
onSuccess: () => console.log('Optimization complete!'),
onError: (error) => console.error('Failed:', error),
});
const handleOptimize = async () => {
resetProgress();
setProgress(0, 'Initializing...');
// Simulate progress
setProgress(25, 'Calculating distances...');
const response = await optimize(request);
setProgress(75, 'Optimizing routes...');
if (response.success) {
setProgress(100, 'Complete!');
}
};
</script>
<template>
<div>
<button @click="handleOptimize">Optimize</button>
<div class="progress">
<div class="progress-bar" :style="{ width: `${progress}%` }" />
<p>{{ step }}</p>
</div>
</div>
</template>
<style scoped>
.progress {
width: 100%;
background: #f0f0f0;
border-radius: 4px;
overflow: hidden;
}
.progress-bar {
height: 24px;
background: #4caf50;
transition: width 0.3s ease;
}
</style>Complete Example
Combining map rendering with route optimization:
vue
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { useRouteOptimization, RouteMapView } from '@route-optimization/vue';
import type { Route } from '@route-optimization/core';
const route = ref<Route | null>(null);
const locations = reactive([
{ lat: 13.7563, lng: 100.5018, name: 'Bangkok' },
{ lat: 13.7467, lng: 100.5352, name: 'Chatuchak' },
{ lat: 13.7307, lng: 100.5418, name: 'Victory Monument' },
]);
const { optimize, isLoading, isSuccess } = useRouteOptimization({
useMockMode: true,
});
const handleOptimize = async () => {
const request = {
shipments: locations.map((loc, i) => ({
id: `shipment-${i}`,
deliveryLocation: { latitude: loc.lat, longitude: loc.lng },
})),
vehicles: [
{
id: 'vehicle-1',
startLocation: {
latitude: locations[0].lat,
longitude: locations[0].lng,
},
},
],
};
const response = await optimize(request);
if (response.success && response.routes?.[0]) {
// Convert optimization result to Route format
route.value = {
stops:
response.routes[0].stops?.map((stop, i) => ({
id: stop.shipmentId || `stop-${i}`,
location: {
lat: stop.location?.latitude ?? 0,
lng: stop.location?.longitude ?? 0,
},
name: locations[i]?.name ?? `Stop ${i + 1}`,
})) ?? [],
};
}
};
</script>
<template>
<div class="app">
<div class="sidebar">
<h2>Route Optimization</h2>
<div class="locations">
<div v-for="(loc, i) in locations" :key="i" class="location-item">
{{ loc.name }}
</div>
</div>
<button @click="handleOptimize" :disabled="isLoading">
{{ isLoading ? 'Optimizing...' : 'Optimize Route' }}
</button>
<div v-if="isSuccess" class="success">✓ Route optimized successfully!</div>
</div>
<div class="map-container">
<RouteMapView
:api-key="import.meta.env.VITE_GOOGLE_MAPS_API_KEY"
:route="route"
:map-config="{
center: { lat: 13.7563, lng: 100.5018 },
zoom: 12,
}"
/>
</div>
</div>
</template>
<style scoped>
.app {
display: flex;
height: 100vh;
}
.sidebar {
width: 300px;
padding: 20px;
background: #f5f5f5;
}
.map-container {
flex: 1;
}
.locations {
margin: 20px 0;
}
.location-item {
padding: 10px;
margin: 5px 0;
background: white;
border-radius: 4px;
}
button {
width: 100%;
padding: 12px;
background: #4caf50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.success {
margin-top: 10px;
padding: 10px;
background: #e8f5e9;
color: #2e7d32;
border-radius: 4px;
}
</style>TypeScript Support
All composables are fully typed. Import types from the packages:
ts
import type {
Route,
Stop,
MapConfig,
OptimizationRequest,
OptimizationResponse,
} from '@route-optimization/core';
import type {
UseRouteMapOptions,
UseRouteMapReturn,
UseMapControlsReturn,
UseRouteOptimizationResult,
} from '@route-optimization/vue';Composition API Patterns
Composable Reusability
Create custom composables for common patterns:
ts
// composables/useOptimizedRoute.ts
import { ref } from 'vue';
import { useRouteOptimization } from '@route-optimization/vue';
import type { Route } from '@route-optimization/core';
export function useOptimizedRoute() {
const route = ref<Route | null>(null);
const { optimize, isLoading, isSuccess } = useRouteOptimization({
useMockMode: true,
});
const optimizeLocations = async (locations: Array<{ lat: number; lng: number }>) => {
const request = {
shipments: locations.map((loc, i) => ({
id: `shipment-${i}`,
deliveryLocation: { latitude: loc.lat, longitude: loc.lng },
})),
vehicles: [
{
id: 'vehicle-1',
startLocation: { latitude: locations[0].lat, longitude: locations[0].lng },
},
],
};
const response = await optimize(request);
if (response.success && response.routes?.[0]) {
route.value = {
stops:
response.routes[0].stops?.map((stop, i) => ({
id: stop.shipmentId || `stop-${i}`,
location: {
lat: stop.location?.latitude ?? 0,
lng: stop.location?.longitude ?? 0,
},
name: `Stop ${i + 1}`,
})) ?? [],
};
}
return route.value;
};
return {
route,
optimizeLocations,
isLoading,
isSuccess,
};
}Reactive State Management
Use Vue's reactivity system with the composables:
vue
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { useRouteMap } from '@route-optimization/vue';
const selectedRoute = ref<Route | null>(null);
const mapRef = ref<HTMLDivElement>();
const { renderRoute, isReady } = useRouteMap({
apiKey: 'YOUR_API_KEY',
mapElement: mapRef,
});
// Automatically render when route changes
watch(selectedRoute, (newRoute) => {
if (newRoute && isReady.value) {
renderRoute(newRoute);
}
});
const routeStats = computed(() => {
if (!selectedRoute.value) return null;
return {
stops: selectedRoute.value.stops.length,
// Add more computed stats
};
});
</script>Best Practices
1. Environment Variables
Store API keys in environment variables:
env
# .env.local
VITE_GOOGLE_MAPS_API_KEY=your_api_key
VITE_GOOGLE_PROJECT_ID=your_project_idAccess in your app:
ts
const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY;2. Error Handling
Use try-catch with proper error handling:
vue
<script setup lang="ts">
import { ref } from 'vue';
const error = ref<string | null>(null);
const handleOptimize = async () => {
error.value = null;
try {
const result = await optimize(request);
// Handle success
} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error';
}
};
</script>
<template>
<div v-if="error" class="error">
{{ error }}
</div>
</template>3. Cleanup
Components automatically clean up, but you can use onUnmounted:
ts
import { onUnmounted } from 'vue';
onUnmounted(() => {
clearRoute();
reset();
});4. Computed Properties
Use computed for derived state:
ts
const routeDistance = computed(() => {
if (!data.value?.metrics) return 0;
return data.value.metrics.totalDistance;
});Nuxt 3 Integration
For Nuxt 3 applications, use client-only rendering for map components:
vue
<template>
<ClientOnly>
<RouteMapView :api-key="apiKey" :route="route" />
</ClientOnly>
</template>Next Steps
- Learn about Vanilla JS Integration
- Explore Route Calculation in depth
- Check out API Reference