Vue.js — Using provide-inject to communicate between parent and dynamic child components

Liu Ting Chun
Mar 4 · 3 min read

I have already written several articles about the parent with dynamic children pattern in Angular. In this article, I would like to write about how you can work on this kind of components in Vue. The parent with dynamic children pattern is a very common pattern for implementing some complicated features, such as third party library integration. Here I would like to explain with an example using Google map:

<GoogleMap v-bind:api-key="key">
<GoogleMapMarker
v-for="(marker, index) in markers"
v-bind:key="`${index}|${marker.lat}|${marker.lng}`"
v-bind:lat="marker.lat"
v-bind:lng="marker.lng" />
</GoogleMap>

The <google-map> is the parent component, which initializes and renders the Google map. The <google-map-marker> is the child component that can be dynamically added to create markers on the map. Here I would like to introduce the provide-inject pair for this kind of use cases.


What is provide-inject?

It is a functionality of Vue that allows you to supply something from your parent component and then request it from your children without knowing where it is actually coming from. You can imagine it as the process to declare some component scope variables, which can be accessed within the component itself and all its children. Here is a simple example with three components:

provide-a.vue, which provides a testValue with value a:

<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
provide: {
testValue: 'a'
}
}
</script>

provide-b.vue, which provides a testValue with value b:

<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
provide: {
testValue: 'b'
}
}
</script>

inject.vue, which will inject testValue:

<template>
<span>{{testValue}}</span>
</template>
<script>
export default {
inject: ['testValue']
}
</script>

When putting them together:

<ProvideA><Inject /></ProvideA> //render <div><span>a</span></div>
<ProvideB><Inject /></ProvideB> //render <<div><span>b</span></div>
<Inject /> //[Vue warn]: Injection "testValue" not found

Example with Google map API

First, we are going to implement the <google-map> component. We initialize the Google map API on component creation. After that, we render the map and supply our API instances to our children via the provide keyword.

<template>
<div class="google-map" style="height: 100%">
<div ref="googleMap" style="height: 100%"></div>
<slot v-if="google && map"></slot>
</div>
</template>
<script>
import GoogleMapsApiLoader from "google-maps-api-loader";
export default {
props: ['apiKey'],
provide() {
return {
getAPIInstance: () => ({
google: this.google,
map: this.map
})
}
},
data() {
return {
google: null,
map: null
}
},
async mounted() {
const googleMapApi = await GoogleMapsApiLoader({
apiKey: this.apiKey
});
this.google = googleMapApi;
const mapContainer = this.$refs.googleMap;
this.map = new this.google.maps.Map(mapContainer, {
zoom: 0,
center: {lat: 0, lng: 0}
});
}
}
</script>

One thing you should be very careful here is that the provide is not reactive. It is run only once on component creation. Hence, if you are supplying reactive data, you may need to use a function. Next, for the <google-map-marker> component, we just retrieve the provided data via inject and create the marker during component creation.

<script>
export default {
props: ['lat', 'lng'],

inject: ['getAPIInstance'],

data() {
return {
markerObj: null
}
},
mounted() {
const { google, map } = this.getAPIInstance();
if (google) {
const Marker = google.maps.Marker;
this.markerObj = new Marker({
position: { lat: this.lat, lng: this.lng },
map
});
}
},
beforeDestroy() {
if (this.markerObj)
this.markerObj.setMap(null);
},
render() {
return null;
}
}
</script>

When to use provide-inject?

The purpose of provide-inject is to fulfil some specific use cases of communication between parent and children. It doesn’t really make thing cleaner or better. You should use it only when it is necessary to avoid adding extra complexity without real benefit. Basically, you will need it only when:

  1. You are building a component with children inside a <slot>

Thanks for reading! Any comments would be highly appreciated. :D


Liu Ting Chun

Written by

Web developer from Hong Kong. Most interested in Angular and Vue. Currently working on a Nuxt.js + NestJS project.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade