Decoupling SPAs from the framework, a practical example

Alberto Varela
6 min readMay 28, 2019

The JavaScript framework war that we are seeing today is harder than the one we’ve seen in the Game of Thrones series. To choose the proper framework is one of the most important decisions we need to take when starting a new project and that’s why there are hundreds of posts on the internet comparing frameworks: reactivity, extensibility, adoption… This is not one of those posts.

The aim of this post is to encourage you to avoid coupling your application to the framework you choose. By doing it you will be able to switch your framework quite easy, which is probably not a common task, but it will help you update it to a new version with very low effort also. Apart from that, a huge advantage will be that it will let you test your application logic without depending on the view.

In order to illustrate how we can achieve it, I’ve created a repository that implements the famous TodoMVC app using the MVP pattern to decouple from the framework. To demonstrate how decoupled it is it includes three implementations that use the same core code: Angular, React and Vue.

I apologize to the pure JavaScript lovers, I’ve chosen Typescript to do the job. I’ve been working with it recently, I love some of its benefits and it helps me following an OO approach. Anyway, a similar way could be followed by using vanilla JS. And remember, this is just my point of view, it’s not evidence ;)

Model-View-Presenter

Wikipedia defines MVP as a design pattern that facilitates automated unit testing and improves the separation of concerns in presentation logic, and I won’t define it better.

The presenter will be in the middle, receiving the user actions from the view and mutating the data from the model, or receiving the data from the model and formatting it to present in the view.

But this is a practical article, if you want more theoretical and interesting readings about the MVP or other design patterns, you can take a look at these articles by Martin Fowler, Derek Greer or Addy Osmani.

The View

Its only responsibility is to show the data that the presenter is sending and to forward the user action events to the presenter. For us, it will be an interface that will define the view behavior and that will be implemented by the chosen framework. A good approach is to create a view for each component, as an example, we will take the ‘TodoItem’ component as an example for the whole post.

There are different approaches when implementing the view, I prefer to use the Passive View pattern and let the presenter control it entirely. In this case, I’m defining an input ‘Todo’ entity, some user events, and some explicit methods to change the view. Those methods shouldn’t have any logic but UI.

The presenter

It’s our orchestra conductor. Its responsibility is to make the proper changes in the model in response to the user actions and to get the proper data depending on the view.

All the presenters of the application extend this base class. As you can see it completely couples the presenter to a view, but this is the way it should work: 1 view <-> 1 presenter, and each presenter will control just one component. Let’s take a look at the presenter of the ‘TodoItem’ component:

As you can see there is more or less a method for each user action defined in the view, and that’s all the logic that the view should implement. In fact, we can define an abstract implementation of the view that sets the contract with the presenter. In my case I have implemented it in the form of a Typescript mixin:

Looking deep into the presenter we won’t see much logic either, this is because I tried to isolate the model of my application in a FLUX/REDUX like architecture. I’ve created my own system because I wanted to make it dependency free, but you can use some common library, like redux itself, to implement it. The aim of using a mediator or a task dispatcher is also to isolate the application use cases from the way we represent them. In the same way I’ve created my own dependency injector, but probably it will be worth to use Inversify or similar library instead.

The basic principle of what I did in every presenter is to subscribe to the application state in every view initialization in order to update it accordingly and to wrap any query or command sent to the model by using a mediator.

The model

In our case, the model will define the data of the application, the repository or the store we are going to use (or at least its interface) and the use cases. Taking into account our previous example I’ll show you a couple of the actions and the ‘Todo’ state container.

This one is a simple use case that should be triggered when the user edits a todo. It just calls the edit action defined in the state container.

The next one is another example. In this case it is not from the ‘TodoItem’ component but from the ‘TodoList’ component. It’s the handler responsible of getting the visible todos. We can consider it like a ‘selector’ in the redux-language.

You can see that both handlers are using some state objects. These objects are what I called the state containers that are the only responsible of mutating the state (reducers in redux-language).

It seems clear what they do, doesn’t it? I’m not going to go deeper on how I implemented the state container and its reactivity, you can check the repo code if you are curious or you can just use a better-tested tool like redux.

What is important to know is that you shouldn’t depend on the framework or in any concrete implementation to persist or retrieve any data. It’s your model who should define an interface that will be implemented depending on the framework or the service you will reach. This is what I defined:

And, in this case, it is the main app component the one who will be in charge of retrieving the data on the application load and to save it on any state change:

Testing

Now, even I didn’t write any HTML line yet, I’ll be able to test the whole functionality of my component by mocking the view. It’s not an end to end test and it will not test the way the data is displayed, but it will cover a great part of the error-prone side of the application. Here you can see a little fragment of the todo.presenter.spec.ts file:

For this project, I’ve just tested the presenters, but you can make also small unit tests covering just the user actions in the model.

The framework implementation

I didn’t talk about any framework yet, but now, I want you to see how easy and similar it is to implement this TodoItem component with any framework that you can choose.

Angular

The angular component needs to implement the view interface we defined before. The methods will just change the value of some class properties that will be used in the view to control how the html is shown. If you are sure that you will be using Angular, you can change the mixin with an abstract class.

React

The React implementation has some differences with the Angular one because of the nature of this framework. The view won’t be updated unless we call the setState method, so, this is what our methods will do.

Vue

The implementation in Vue is more similar to the one with Angular. By using Typescript, we can just update the class properties directly and the reactivity will update our view. The most different thing using Vue is that we need to create a middleware component to extend using the mixin, because is how Vue works with Typescript.

Looking at the three together I expect that you will be able to see how similar they are. Of course, the templating or the state management of each one is quite different, but it’s just a matter of adapting them to our presenter.

My todomvp repository holds the full implementation of the TodoMVC app with the three frameworks, not just the ‘TodoItem’ component. Check it if you want and poke me on Twitter or by email if you have doubts or if you found any issue.

Use the framework and don’t be used by it.

Originally published at https://www.berriart.com on May 28, 2019.

--

--