I am new to Vue and my understanding of v-model is still limited. Now. I am trying to make a Vue 3 composable version of the example provided in https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-vue/. The problem I have is that the props.modelValue does not seem to update. Would you know what is wrong in the the parent/child .vue ?
Here is the parent App.vue:
<script setup>
import Map from './components/Map.vue';
import '../node_modules/mapbox-gl/dist/mapbox-gl.css';
import { ref, reactive, defineModel } from 'vue';
const model = defineModel()
const location = reactive(
{ lng: -71.224518,
lat: 42.213995,
bearing: 0,
pitch: 0,
zoom: 9 },
)
</script>
<template>
<div id="layout">
<div id="sidebar">
Longitude: {{ location.lng.toFixed(4) }} | Latitude: {{
location.lat.toFixed(4) }} | Zoom: {{ location.zoom.toFixed(2) }} |
<template v-if="location.bearing">
Bearing: {{ location.bearing.toFixed(2) }} |
</template>
<template v-if="location.pitch">
Pitch: {{ location.pitch.toFixed(2) }} |
</template>
<button
@click="location = { lng: -71.224518, lat: 42.213995, zoom: 9, pitch: 0, bearing: 0 }"
>
Reset
</button>
</div>
<Map v-model="location" />
</div>
</template>
<style>
#layout {
flex: 1;
display: flex;
}
#sidebar {
background-color: rgb(255 255 0 / 50%);
color: #fff;
padding: 6px 12px;
font-family: monospace;
font-weight: bold;
z-index: 1;
position: absolute;
top: 0;
left: 0;
margin: 12px;
border-radius: 4px;
}
</style>
Here is the child Map.vue:
<script setup>
import mapboxgl from 'mapbox-gl';
import { ref, watch, onMounted, onUnmounted } from 'vue'
mapboxgl.accessToken = <UserAccessToken />; // here you need your mapbox access token
const props = defineProps({
modelValue:
{
type: Object,
required: true
}
});
const emit = defineEmits(['update:modelValue']);
const mapContainer = ref(null);
const thismap = ref(null);
onMounted(() => {
const { lng, lat, zoom, bearing, pitch } = props.modelValue;
const map = new mapboxgl.Map({
container: mapContainer.value,
style: "mapbox://styles/mapbox/dark-v11", //'mapbox://styles/mapbox/streets-v12',
center: [lng, lat],
bearing,
pitch,
zoom
});
const updateLocation = () => {
emit('update:modelValue', getLocation()); // here I think it is the issue
}
map.on('move', updateLocation);
map.on('zoom', updateLocation);
map.on('rotate', updateLocation);
map.on('pitch', updateLocation);
thismap.value = map;
});
onUnmounted(() => {
thismap.value.remove();
thismap = null;
});
watch (() => props.modelValue, (next) => {
const curr = getLocation();
const map = thismap.value;
if (curr.lng != next.lng || curr.lat != next.lat)
thismap.setCenter({ lng: next.lng, lat: next.lat });
if (curr.pitch != next.pitch) thismap.setPitch(next.pitch);
if (curr.bearing != next.bearing) thismap.setBearing(next.bearing);
if (curr.zoom != next.zoom) thismap.setZoom(next.zoom);
});
function getLocation() {
console.log("is this ran?")
//console.log(props.modelValue)
// console.log(thismap.value.getZoom())
return {
...thismap.value.getCenter(),
bearing: thismap.value.getBearing(),
pitch: thismap.value.getPitch(),
zoom: thismap.value.getZoom()
}
}
</script>
<template>
<div ref="mapContainer" class="map-container"></div>
</template>
<style>
.map-container {
flex: 1;
}
</style>
It looks like the object does not get updated after the emit, cause props.modelValue does not change values. Also, if there is anything else I am doing wrong here, I would love to receive your feedback.
v-model
is syntax sugar for:
<Map :modelValue="location" @update:modelValue="location = $event" />
The only way for the reactivity to work this way is to make location
a ref. ref
should be chosen over reactive
when an object is expected to be fully replaced at some point:
const location = ref({...});
This works with event listener due to ref unwrapping in templates, location = $event
actually means location.value = $event
.