Stores with Dependency Injection in Vue 2 with Inversify

Fabrizio Mele
4 min readMar 1, 2022

--

I’m a web developer and by far the best developer experience I ever had is with Vue 2, with Typescript, with decorators and class components.

I find just much more clear, concise and eloquent declaring props and state as class attributes, methods as class methods (duh), and computed properties as getters. Using Vue the typical store choice is Vuex. Pinia is an alternative but Vue 3 is a big no, and Composition API + Vue 2 is an even bigger no. I’m not digging deeper into this, but the dev chapter of my company is seriously considering switching to Angular to avoid using Vue 3. We kinda hate it.

Anyway, Vuex is a nice store, with a big drawback: its complicated, and it has an horrible way to commit actions and mutations (by name, (like php?!))…

The case

While scaffolding a new view, a fairly complex one, in a company project, a very complex one, we had to get smart about the business logic of the view: long story short, the view has a big data table showing different records populated from a backend API, and various filters scattered in split panes, modals and whatnot. It seemed a clear case of moving the logic inside the store:

  1. the data table watches a getter on the store that returns Model[];
  2. the various filters update the FilterObject stored inside the store;
  3. the store updates its internal state calling the API with some filter parameters every time the FilterObject is updated.

This way the getter returns always a filtered Model and everything is totally decoupled, to allow designers to tinker with the UI without making us dev really unhappy moving around events, wrappers and whatsoever.

The store

Having previously tried Inversify as a replacement for http mixins (a rather bad thing), it came to us that it would be much easier to implement and use as a store a singleton service.

The idea is simple: the store is a Typescript class which extends Vue (so it is technically a Vue component, more on this later). The store is bound to the IOC container in singleton scope. The store is injected inside every component which updates the FilterObject or displays the Model[] .

It looks like this:

Glossing over missing type declarations its working are quite simple: we leverage Typescript getters and setters to react to attribute updates: every time a the filter is updated we advertise the filter update event and then call the API (hopefully with a debouncer). The API call will update the items attribute and advertise the fact that the items may have changed.

Extending Vue (i.e. declaring the store as a Vue component) allows us to leverage Vue events to advertise stuff! Event bus baby!(line 31)

Now we just need to:

  1. singleton this class with more Inversify
  2. subscribe to relevant events from the view components.

The container

Now we need to bind our shiny new Store to the container. Here we encountered some roadblocks, but it turns out it was a bad written guide!

In the main.ts where we declare and mount the Vue instance we must initialise the dependency container. Not that reflect-metadata is imported before everything else. Weird errors could be thrown otherwise.

not before building the container:

This functions makes a bit clearer the @inject syntax found in the Store.ts . We will anyway see more on that just now. Note that the Store is declared in singleton scope: this means that it will be instantiated just once. Everyone asking for the Store object anywhere in the single page application will get the same instance, allowing us to use it as a store!

The components

Now that we have a IOC container and some injectable stuff fired up and running we can do fancy stuff without anything more than an @injectdecorator.

For example, a component updating the filter could be:

Vue on and off methods can be used to hook into the store events. This SearchBox components can be dropped anywhere on a view and it will always be synchronised with the store singleton.

Note that the store is injected with a decorator, it is possible to manually get it from the container by calling container.get inside mounted . I find decorators much more elegant and concise.

On the other side, with some imagination we can image this component dynamically refreshing its contents as the store updates its state:

Even more quick and easy. Just subscribe to the items-updated event and the component will (not so) magically be synchronised with the search box.

Conclusions

Obviously this is a very simple example, but it should satisfy the need to se a complete example of Inversify DI in actions with prop injection in class components.

The store could contain more logic and even actions (as simple methods), even if we prefer to keep inside it the logic strictly related to items searching and filtering. Adding stuff to it just because it’s easy and it works can cause “big store syndrome”.

There are obviously limitations: for one any persistence must be done by hand. We did not yet encounter this requirement, as this is mostly a runtime support for a view. Also, there being no mutations there is no history, so this is not the thing you are looking for if you need history.

--

--

Fabrizio Mele

Studente, dev @ monade.io, podcaster. Curo Il Podcast di Alessandro Barbero, mi piacciono xkcd, i gatti, e le serie tv in lingua originale. — Desenzano BS