Dependency Injection

in Vue.js

Who Am I?

  • Alex Riviere
  • UI/UX 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

Props Down, Events Up

  • Explicitly Defined Props and Events
  • Creates reusable components
  • Doesn't rely on outside data structures
  • Easily testable

Global Store

<parent-component />
<child-component />
<grand-child-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 of All Evil

<parent-component />
<child-component />
<grand-child-component />

$root.evil

$root of All Evil

<parent-component />
<child-component />
<grand-child-component />

$root.evil

<grand-parent-component />

$parent Knows Best

<parent-component />
<child-component />
<grand-child-component />

$parent.knowledge

$parent.$parent.knowledge

(Has a bit of data called knowledge)

$parent Knows Best ?

<parent-component />
<grand-child-component />

$parent.$parent.knowledge?

Dependency Injection

<parent-component />
<child-component />
<grand-child-component />
provide
inject
<child-component />
<child-component />
<grand-parent-component />

Dependency Injection

<providing-component />
<child-component />
<injecting-component />
<child-component />
<child-component />
<root-component />
_provide.bit_of_data
inject:['bit_of_data']
_provide.bit_of_data?
_provide.bit_of_data?
_provide.bit_of_data?
_provide.bit_of_data!
this.bit_of_data

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

https://github.com/LinusBorg/vue-reactive-provide

convext

A Context-API like interface for Vue.js

https://github.com/fimion/convext

(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.

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?