Would you like to buy me a ☕️ instead?
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.
- Vue.js Single File Component Factory
- The IoC Container Pattern with Vue.js
- Dependency Injection in Vue.js Applications
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.