Dependency Injection in Vue.js with Functional Component Factories

If you are a regular reader of my blog (thanks to all of you), you may have noticed that many of my articles are about decoupling components from their dependencies. Over the past few months I have written a few articles on this subject.

I regularly use variations of the approaches mentioned in these articles in my daily work. But today I share with you an additional way to inject dependencies into Vue.js components that I find very interesting: dependerncy injection via functional components and component props.

The usual approach with props

Let’s first take a look at how we can use props to inject an API service implementation into a generic listing component.

<template>
  <ListingContainer
    :service="productService"
  />
</template>

<script>
// src/components/ProductListing.vue
import productService from '../services/product';

import ListingContainer from './ListingContainer';

export default {
  name: 'ProductListing',
  components: {
    ListingContainer,
  },
  created() {
    this.productService = productService;
  },
};
</script>

In this example the productService would be a an object exposing at least a fetch() method for fetching a list of objects from an API endpoint.

Having to declare this.productService = productService in the created() hook is not very pretty in my opinion, so there must be a better way to do this.


Do you want to learn more about advanced Vue.js techniques?

Register for the Newsletter of my upcoming book: Advanced Vue.js Application Architecture.


Injecting props via a functional wrapper component

In the following example you can see how we can use a generic containerFactory() function which makes it a lot more straightforward to inject a service via a property.

<script>
// src/components/ProductListing.vue
import containerFactory from './factories/container';
import productService from '../services/product';

import ListingContainer from './ListingContainer';

export default containerFactory(ListingContainer, {
  service: productService
});
</script>

A lot less boilerplate code, isn’t it? Next you can see the implementation of the containerFactory().

// src/components/factories/container.js
export default (Component, props) => ({
  functional: true,
  render(h) {
    return h(Component, { props });
  }
});

With only 6 lines of code we are able to conveniently inject services and other dependencies without having to add a lot of boilerplate code to our components.

As @karlito40 pointed out on Twitter, the container factory could be extended by automatically passing all its props to the wrapped component.

 // src/components/factories/container.js
 export default (Component, props) => ({
   functional: true,
+  props: Component.props,
-  render(h) {
+  render(h, context) {
-    return h(Component, { props });
+    return h(Component, {
+      props: { ...context.props, ...props },
+    });
   }
 });

Depending on your use case, this can be a variant to choose instead.

Wrapping it up

I think this is a nice little improvement over having to abuse the created() hook for making dependencies available in the <template> section of a component every time you have to inject a service or some other dependency into a component.


Do you want to learn how to build advanced Vue.js applications?

Register for the Newsletter of my upcoming book: Advanced Vue.js Application Architecture.



Do you enjoy reading my blog?

You can buy me a ☕️ on Ko-fi!