Service architecture in Vue 2

Yep, I know Vue 3 is in stable and even Nuxt has finally been updated. But it is the 3rd Vue with its provider / inject prompted me to look for a solution on how to conveniently encapsulate business logic in Vue 2.

Introduction

Let’s start with the question “Why can’t you just upgrade to Vue 3?”. Quite simply, some projects have such a level of connection with other projects that it cannot be done quickly. Somewhere, specific packages may be used that were written only for the 2nd version of Vue. Somewhere developers are satisfied with Vue 2, and even if they switched to Vue 3, they would continue to write on the Options API (I will say right away, I did not check my solution on Vue 3, so I’m not sure if this approach will work there, but generally should).

How to encapsulate logic

On the Internet and participating in questions asked in chats (like “Vue.js – Russian-speaking community”), and just working, most often I saw several solutions. Let’s take a look at their pros and cons.

Render to Vuex/Pinia/Other state manager

Surprisingly one of the most popular answers, as soon as you need to share some data – take it to the store.

It comes to the ridiculous, the person asked how it is possible to pass the search string from the component to the neighboring one (they have a common parent). Several solutions were thrown at him, and the person chose Vuex, because in his opinion it is easier.

Another example, on the project I saw a page that saved temporary form data (and to some extent cached for the session) using .. Vuex. I think it makes no sense to explain in detail what this is fraught with, but there were a lot of bugs related to this, I had to completely redo this page.

Pros

  • Often advised, easy to find help

  • Using vuex is intuitive

  • Possibly instills a sense of security

  • In general, it is easy to read, it is clear where the data comes from

  • You can get only the data that you need in a particular component

Cons

  • Causes bugs if you try to store temporary data (which should be cleaned, or changed depending on the context)

  • It is impossible to reuse the logic (one module per site, you can’t get away from this, you can’t create an additional instance) only if you do not make 10 identical modules

  • Using this logic for a short time, it will still remain in memory, although it is no longer required.

  • The data is not protected, any component can change it with a direct call to state

Mixins

This is the second most popular method of removing some kind of general logic, but if the answer about Vuex is frequent with the problem of data propagation, then mixins are a popular method of removing logic. Although, according to my impressions, people began to use it less often, due to the fact that the official library recommends being as careful as possible about this method.

Pros

  • again intuitive, looks like a piece of a component

  • there is no other way to integrate a common hook, or framework-specific elements

Cons

  • If writing a mixin is really intuitive, then reading a component that uses a mixin is completely unreadable. You select a variable, look for it in the file, but it simply does not exist, and this little line mixins very easy to miss.

  • It’s easy to overwrite mixin functionality by accidentally using the same property/method names

  • Sometimes mixins use properties that should be defined in the component, and you may not know this.

  • Unable to share dataonly if you don’t put them in vuex)

  • Only suitable for code reuse, you won’t be able to do something global with a mixin

  • You can’t choose which methods/properties you need

Provide / injection

This is no longer the most popular way, not everyone knows about this option. Many people know that this is in Vue 3, but it appeared in Vue 2 provide in version 2.2.0. You can read more at link.

In general, the technology allows you to pass either an object or a function that returns an object. So I think there is a possibility, if you play around, to transfer the same instance of the class. But it might cause some problems.

Pros

  • Allows you to transfer data to any depth

  • You can choose what you will inject

Cons

  • Requires a common parent, in the worst case you have to do it on the page or in App what messes up the code

  • Thus, it will not be possible to reuse the code, it’s more about data distribution

  • If you make an instance of the class (somehow, I have not tried it), then you will take it entirely for yourself, there is no way to choose what you need

  • It is not intuitive and not obvious what will happen with reactivity, where to change this data, and in general raises many questions

Why did I mention provide/inject at the beginning

When I wrote in Vue 2, I didn’t even have a rough idea of ​​how logic could be encapsulated. It seemed to me that in the format in which we make components, it would be somehow inconvenient, incomprehensible, and somehow crutch.

Vue 3 with the Composition API opened up a new milestone for me, where you can instantiate a class, wrap it completely in reactive, do provide right at the stage of creating an application (i.e. in index.js conditional, which was understandable).

But this solution did not suit me completely, I wanted to do it somehow differently. While I was thinking about this, I switched to another project that is written in Vue 2, and this prompted me to look for a solution.

Move to functions

This way is very well suited for simple functions that are not related to anything else, validators or formatters. From the point of view of logic encapsulation (and usually in this case we are talking about a connected process, where several stages take place at different times) this is not the most appropriate way.

Pros

  • Easy to use, import the function and use

  • Familiar and recognizable

Cons

  • For a linked process, you have to pass data from one function to another, which worsens readability

  • If you try to store data in some variable outside of functions, it will not be clear what is there

Global variables

In Vue 2, I often wrote the class I created that stores the Axios instance into global variables. This is quite convenient for a module that is used everywhere.

Pros

  • Convenient use through this

  • You can write an instance of a class to a global variable, which increases your possibilities

Cons

  • Even if you write an instance of the class, then it will be one

  • It’s not clear if the data will be reactive, it’s not entirely obvious

  • Usage through this convenient for those who know what data in the project is global. For the rest, you still have to guess where to look for this entry, and what lies under the variable.

Bring to class

Seems like the most convenient method, but the articles I found suggested either making a singleton or making static methods and inlining them like that

methods: {
  someAlias() {
    Class.someMethodFromClass();
  }
}

I don’t like this method because there are a lot of garbage functions, there is no understanding of how to embed data and change it. And in general it so happened that I did not find a single article that would show a complete solution, taking into account all kinds of cases.

So I decided to create this solution myself.

introductory

I would like to be able to create a service that

  1. Created an instance only on request, allowed to create multiple instances, could delete the desired instance

  2. It was clear where the data or methods came from, so that when searching through the name file, the source could be found

  3. The data must be consistent, when changed in one place, in all other places they must become the same

  4. If the data is used in the component, then it should be possible to make it reactive, that is, when displayed in a template, the component must be re-rendered after changing

  5. It should be possible to protect data so that it can be changed only from the class, to prohibit changes from components

  6. It should be possible to change data from the component, set a validator for such changes

  7. It should be possible to get an instance of a component for some specific action, though I don’t really support it.

  8. It should be possible to embed this class in any component without the obligation to have a common parent

Spoiler

I was able to implement such logic, it turned out it was so simple that it was even ridiculous. And now I want to tell you about it and provide factory functions for wrapping such a class so that you can also conveniently use classes.

This is part 1 of the article about the implementation of the service in Vue 2.

In part 2, I want to talk about the details of designing a class, how to work with different data types, how they are embedded in a component and acquire reactivity, how to make a getter for a property / properties (analogue computed) and pass it to the component.

In part 3, I will talk about instances, how to regulate creation and destruction, show my factory functions for class binding, how to make it convenient to pass such properties to a component.

Optional

In part 4, I would like to talk about what kind of service can be considered good, what should be put into a service so as not to shoot yourself in the foot later, how to test them and how to test components that use the service.

I will look at your reaction, whether it will be interesting for you to read about it.

If you don’t want to wait, then link to repository with a working solution, there are detailed comments about the implementation. Most interesting files: himself Classand of course strapping functions.

This option is still in the draft stage, I am preparing documentation for it. With practice, I will refine it, plus in the course of writing the following parts, I can remember some case that I didn’t take into account.

Similar Posts

Leave a Reply