Dependency Injection
in Vue.js
Who Am I?
- Alex Riviere
- Design Engineer for NexTraq
- Co-Organizer for Atlanta Vue.js Meetup
WARNING
Advanced Vue.js Concepts Ahead
May Cause:
- Confusion
- Sense of Wonder
- Smug satisfaction of knowing more than others.
Methods of Passing State Between Components
- Props/Emit
- External Store (Vuex)
- $root/$parent (Not Advised)
- Provide/Inject
Props Down, Events Up
<parent-component>
<child-component>
<grand-child-component />
$props
$props
$emit
$emit
</child-component>
</parent-component>
Props Down, Events Up
- Explicitly Defined Props and Events
- Creates reusable components
- Doesn't rely on outside data structures
- Easily testable
Global Store
<fox-component />
<whale-component />
<unicorn-component />
Vuex $store
state
state
state
mutation
mutation
mutation
Global Store
- Useful for large applications with shared state
- Can be used along with props/$emit
- Implicitly uses $store
- Relies on external dependency
- Not ideal for publishing modules
$root
<parent-component>
<child-component>
<grand-child-component />
$root.bitOfData
</child-component>
</parent-component>
$root of All Evil
<parent-component>
<child-component>
<grand-child-component />
$root.bitOfData?
</child-component>
</parent-component>
<new-root-component>
</new-root-component>
$parent Knows Best
<parent-component>
<child-component>
<grand-child-component />
$parent.knowledge
$parent.$parent.knowledge
(Has a bit of data called knowledge)
</child-component>
</parent-component>
$parent Knows Best ?
<parent-component>
<grand-child-component />
$parent.$parent.knowledge?
</parent-component>
Dependency Injection
<parent-component>
<grand-child-component />
provide
inject
<child-component>
<grand-parent-component>
</child-component>
</parent-component>
</grand-parent-component>
Dependency Injection
<providing-component>
<injecting-component />
<child-component>
<child-component>
<root-component>
_provided.bit_of_data
inject:['bit_of_data']
_provided.bit_of_data?
_provided.bit_of_data?
this.bit_of_data
</child-component>
</child-component>
</providing-component>
</root-component>
SHOW ME
HOW ALREADY!
<div>
<p>This is the count:</p>
<p>{{count}}</p>
<slot />
<button @click="count++"
class="button">
Increment!
</button>
</div>
SimpleProvide.vue
import SimpleInject
from './SimpleInject';
export default {
name: 'simple-provide',
data(){
return {
count: 1,
};
},
provide(){
return {
'count': this.count
};
},
components: {
SimpleInject,
},
}
Template
Script
<div>
<p>
This is the injected count:
</p>
<p>
{{count}}
</p>
</div>
SimpleInject.vue
export default {
name: "simple-inject",
inject: ['count'],
}
Template
Script
Simple Demo
How do we make this Reactive?
RxJS!
NO!!
<div>
<p>
This is the parent text:
</p>
<p>
{{text}}
</p>
<input type="text"
v-model="text">
<slot />
</div>
ReactiveProvide.vue
import ReactiveInject
from "./ReactiveInject"
export default {
data() {
return {
text: "Hello World",
}
},
provide() {
return {
getText: ()=>this.text,
setText: v=>this.text=v,
}
},
components: {ReactiveInject},
}
Template
Script
<div>
<p>
Injected text:
</p>
<p>{{ text }}</p>
<input type="text"
v-model="text">
</div>
ReactiveInject.vue
export default {
name:"reactive-inject",
inject: [
"getText",
"setText",
],
computed: {
text: {
get() {
return this.getText()
},
set(d) {
this.setText(d)
},
}
},
}
Template
Script
Reactive Demo
Helpful Packages
vue-reactive-provide
Plugin/Mixin wrapping Vue's static 'provide/inject' feature allowing to easily pass reactive data to children
(I had to put the V somewhere.)
Where should I use this?
When it is implied that a certain bit of data will exist, and can be passed behind the scenes. A good example is tightly coupled components where they only work together.
Let's look at an example using
Google Maps.
<div class="google-map-loader">
<div ref="google-map"
class="google-map">
</div>
<template v-if="mapLoaded">
<div style="display:none"
aria-hidden="true">
<slot />
</div>
</template>
</div>
GoogleMap.vue
const maps = window.google.maps;
export default {
data(){
return {mapLoaded: false,}
},
props:['center','zoom'],
provide(){
return {map: ()=> this.map,}
},
mounted() {
this.$nextTick(()=>{
this.map = new maps.Map(
this.$refs['google-map'],
{
center:this.center,
zoom:this.zoom,
});
this.mapLoaded = true;});
},
}
Template
Script
<i style="display:none;"
aria-hidden="true">
<slot />
</i>
GoogleMapMarker.vue
const maps = window.google.maps;
export default {
props:['position'],
inject:['map'],
provide() {
return {
marker: () => this.marker,
}
},
mounted(){
this.marker = new maps.Marker({
position:this.position,
map:this.map(),
})
}
}
Template
Script
<div>
<slot />
</div>
GoogleMapInfo.vue
const maps = window.google.maps
export default {
inject:['map','marker'],
mounted(){
this.$nextTick(()=>{
this.info = new maps.InfoWindow()
this.info.setContent(this.$el)
this.info.open(
this.map(),
this.marker()
)
})
}
}
Template
Script
Google Maps Demo
But what about Vue 3?
import {ref, provide} from 'Vue';
export default {
// ...
setup(){
const someData = ref('Hi.');
provide('someData', someData);
return {
someData,
}
},
// ...
}
Vue 3 Composition Api
import {inject} from 'vue'
export default {
// ...
setup(){
const data = inject('someData');
return {
data,
}
},
// ...
}
Providing Component
Injecting Component
Questions?
Dependency Injection in Vue.js - DevNexus 2020
By Alex Riviere
Dependency Injection in Vue.js - DevNexus 2020
- 588