Markus Oberlehner

Dependency Injection in Vue.js with Functional Component Factories


If you are a regular reader of my blog, you may have noticed that many of my articles are about decoupling components from 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: dependency 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.