This is a short article on what you can do with provide/inject
TypeScript support in Vue 3 and @vue/composition-api
library for Vue 2.
Bad Fuel Injector Symptoms: Saturn Vue The symptoms of bad fuel injectors are going to feel almost the same as a bad fuel pump or fuel filter. That’s why it’s important to check the fuel pressure at the rail before replacing injectors, otherwise you may end throwing good money after bad. Vue Injector 3.3.1 download free - Vue Injector - a Vue.js library - free. software downloads - best software, shareware, demo and trialware.
Providing Type Safety
Initially when I started using provide/inject
with the composition API I wrote code that looked like this:
Do you spot the problem?
When working with TypeScript, I view the as
keyword like an escape hatch where you don't know how to make TypeScript understand the type of the thing you are dealing with. And while I still use it occasionally, I like to avoid using as
as much as possible (pun intended).
For context, I tweeted about this very topic some time ago, I was using the composition API in a production TypeScript app and needed to provide type information for provide/inject
and I was going to do that myself but found that Vue already has a utility type called InjectionKey<T>
which did exactly what I needed.
TIL 🤓 You can enable types 👕 for the provided/injected values 💉 with Vue's composition API by using the `InjectionKey` generic type 👇#vuejs#typescriptpic.twitter.com/NQt0Fmoxfi
— Abdelrahman Awad (@logaretm) October 25, 2020That means you need to have a special constants file holding the Injectable keys, then you can use the InjectionKey<T>
type to create symbols that hold the resolved injected property type information.
The great thing about the InjectionKey
type is that it works in both directions. It provides type safety to provide
which means if you try to provide an incompatible value with that key, TypeScript will complain:
And on the receiving end, your inject
will also be typed correctly:
One thing to note is that the inject
function produces the resolved type in union with undefined
. This is because there is the possibility that the injection is not resolved. It's up to you how you want to handle it.
To get rid of undefined
you need to pass a fallback value to the inject
function. What's cool here, is that the fallback value is also type checked:
Providing Reactive Values
While you can provide plain value types, they are not that useful as usually you need to react to these values changing. You can create reactive injections as well with generic types.
For reactive refs created with ref
you can use the generic Ref
type to type your InjectionKey
, so it's a nested generic type:
Now when you inject
the ProductKey
into your components, the resolved property will have the Ref<Product>
type or undefined
like we discussed earlier:
Dealing with undefined
I already mentioned how to deal with undefined
Update for mac office 2011. being resolved with your plain values, but for complex objects and reactive objects you cannot really offer a safe fallback value.
In our example we tried resolving a Product
context object, if that injection doesn't exist then maybe there are a more severe underlying issue that justifies failing with an error if the injection is not found.
2003 Vue Injectors Replacement
By default Vue displays a warning if it did not resolve an injection, Vue could've chosen to throw errors if an injection is not found but Vue can't really make the assumption about whether the injection is required
or not, so it's up to you to make sense of unresolved injection and the undefined
value.
For optional injected properties, just provide a fallback value or you can use the optional chaining operator if its not that important:
But for required ones like our Product
type, we can do something as simple as this:
Throwing errors is one way to take advantage of TypeScript's exhaustive checking feature. Because we handled the undefined
component early on, there is no way for the code to reach the last line without the product
being actually resolved.
Let's make this more re-usable, let's create a function called injectStrict
that does all of that for us:
Ve Injector Pump
And now if you use that instead of inject
, you will get the same safety in a modular way without having to deal with pesky undefined:
Conclusion
I think provide/inject
will surge in popularity especially with the composition API, and knowing the TypeScript capabilities of them will make your code easier to maintain and much safer to work with.
Thanks for reading! 👋
Would you like to buy me a ☕️ instead?
I often wonder how to decouple certain parts of an application best. At first, this seems pretty straightforward in the context of Vue.js applications. You have components and components pass down properties and emit events; that’s it. Right? Yeah, kind of. But also kind of not. Soon there will be the need to access global state or retrieve data from an external resource via an API. If we don’t be careful how we tackle these challenges, there will be the time when we realize that our components, which we planned to be nicely decoupled, use many external dependencies and are anything but decoupled.
Tight coupling vs. loose coupling Vue.js components
In this article, we take a look at what patterns exist in Vue.js applications to provide dependencies to components. We examine the advantages and disadvantages of each approach and what alternatives there are to each. Also, we learn that certain methods are very well suited for particular use cases but not so well for others. After reading this article, you should have enough tools in your utility belt to decide whether a hammer or a Phillips screwdriver is a better choice for a specific use case.
Table of Contents
Importing modules
Let’s start with the first method of “injecting” functionality into a Vue.js component: module imports. Although one might argue that this is the complete opposite of dependency injection, I use the literal sense of the words. Even if you don’t agree with me calling this a form of dependency injection, I think it’s still worth taking a closer look at the pros and cons of this practice.
There are a few things that are wrong with this example above. First of all the name: LoginButton
. We could make it much more reusable by calling it SubmitButton
instead. The main problem, however, is the tight coupling with the login()
method of the user service. By decoupling this component from this hard-coded dependency, we can make it much more useful.
In the above example we also use imports but this time we use it to import base components which are the basic building blocks of our application. Think of them as HTML elements on steroids. You might even consider to globally register such components (although I’m personally not a fan of this practice).
Here you can see a container component. While it is still technically the case that we closely couple this component with the user service by importing it directly, we allow ourselves to do so in container components. There has to be some place(s) in our application where the coupling happens. Think of container components as poor mans IoC containers. These container components are deliberately tailored to a very specific use case.
Recommendation: try to only use import
in container components or for importing base components. Use sparingly otherwise.
Decoupled component composition
Although it is usually not called this way, slots in Vue.js are actually an implementation of the IoC pattern. Let’s take a look at the following two code examples to see what I mean.
In the this example, the ProductListing
component itself fetches the data it needs. This means it’s tightly coupled to the product service. In the following example we use the container component pattern to solve this.
The container component above is responsible for assembling all the necessary components and pass in the data they need and additionally listen to events they emit. The generic ListingLayout
component is responsible for basic styling and layout of the listing. We successfully decoupled our very specific components and replaced them with highly reusable generic components.
You can see that we could easily reuse the NavFilter
, EntityList
and NavPagination
components because they’re completely decoupled from any data fetching logic. It would be very straightforward to create a new ArticleListingContainer
component by simply reusing the components that already exist.
Recommendation: using component composition with slots should be the preferred way of how to decouple components from logic in order to make them reusable.
Plugins
Another possibility to inject functionality into Vue.js applications are plugins.
Above you can see the recommended way of how to globally register a variable with a plugin. You might consider to bind $userService
directly to Vue.prototype.$userService
but this has the downside that you bind it to every instance of Vue
which, depending on the situation, might or might not be what you want. Next you can see how to use the now globally available $userService
.
Plugins are useful, in the same way as hammers are useful. They are a powerful tool in the toolbox of any Vue.js developer. But with great power comes great responsibility. And as we all know: if the only tool we’re familiar with is a hammer, everything looks like a nail.
Making the $userService
globally available makes it very easy to use it everywhere you want. Sounds great? Not so much in my ears. Let’s see why this pattern can be problematic. First, it promotes tight coupling in components that ideally should not be tightly coupled to an API service or some other function that causes side effects. But it’s just so easy to do that with a variable which is globally available everywhere in your application.
Another concern is that it’s not immediately obvious where $userService
is coming from, it’s just there. But you can compensate that by keeping a 1:1 mapping of plugin names to the global variable name (e.g. user-service.js
-> $userService
).
Furthermore, because of the global nature of plugins, there might arise namespacing issues. This may not look like a big deal at the beginning of a project, but once a lot of people are working on the same code, it could quickly become a very real problem.
This pattern is best used for high level functionality which is global in its nature. Vue Router might come into mind for a typical use case. But also with official plugins like Vuex and Vue Router I personally do not love this pattern because it often leads me to building tightly coupled components which are hard to reuse.
Recommendation: use custom plugins sparingly. Additionally you should absolutely avoid accessing global variables from within base components and other mostly presentational components.
Provide / inject
UPDATE: with the Vue 3 Composition API, we are now able to inject dependencies using the Context Provider Pattern automatically.
I feel provide / inject has kind of a bad reputation in the Vue.js world. Unjustified, in my view. I guess one of the reasons why that is, is because variables provided to child components that way are not reactive. But thats a plus in my books. If they were reactive, this feature would be abused to break the one way data flow pattern Vue.js enforces. Another reason might be that in the official Vue.js documentation it is recommended to not use this pattern in generic application code. Unfortunately, it is not stated why this is so, because in my opinion you can do much worse things with plugins.
But the way we want to use provide / inject, we don’t care about reactivity at all: we want to inject dependencies, like the product service we’ve seen earlier, to child components.
Above you can see the ListingContainer
component which is a generic version of the ProductListingContainer
from an earlier example. By injecting the fetch()
method we can reuse the component for every content type we like.
If you would like to learn more about this approach, you can take a look at my follow-up article: The IoC Container Pattern with Vue.
The ProductListingProvider
is responsible for providing the correct fetch()
method to its children. In this example we could’ve used properties as well, but I find it cleaner to use provide / inject for passing functions to child components.
This pattern solves a couple of problems we’ve faced with using plugins. First of all it’s more obvious where a certain method is coming from because we have to reference it in the inject
section of our consumer component.
Although it might not seem like a big deal, but having to explicitly inject dependencies in consumer components forces you to think about it more closely which might lead you to decide that a certain component should not be able to trigger side effects.
Furthermore it makes it a lot easier to deal with namespacing issues. When using inject
we can also use the object syntax to rename injected properties.
Recommendation: use this pattern instead of plugins whenever it’s suitable but also don’t overuse it. If you catch yourself using this pattern to pass down dependencies over multiple levels a lot, there might be a problem with your overall architecture.
Functional component factories
Another way to deal with dependency injection in Vue.js is to use functional component factories. I’ve written a separate, in-depth article on this topic.
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 global state
If you’re interested in how to deal with global state, you can read my article about how to decide when to use Vuex, or some alternative solution for handling global state.
Third party tools
Even though, as we have seen, Vue.js offers a lot of ways to inject dependencies into components, there are also some additional third-party tools. One of the most notable is vue-inject. Most of its functionality can also be achieved by using one of the methods above but additionally this package makes it possible to inject component constructors. There might be situations where this is preferable instead of using slots which only makes it possible to inject component instances. Free photoshop for mac download full version.
Like What You Read?
Follow me to get my latest Vue.js articles.
Wrapping it up
As we’ve seen, there are a lot of ways of how to deal with dependency injection in Vue.js applications. Although I certainly have some strong opinions about various practices, it should be stated that there is no one true way of how to do this. Depending on the type of the application you build, and also depending on your personal preferences, you might use the one or the other approach.
My goal with this article is to extend the available tools in your and my toolbox so the next time we face a Phillips-head screw problem we don’t immediately reach for our good old hammer and start hammering away.