Forget about react/angular/flux/redux, design your app first — part 1/3

Part 1: architecture and the view component

In this article, I will show you how to use a few lines of code (≈160) to build a UI-library-independent front-end application, but still benefit from all the features of redux pattern.

With it, you can easily change the UI implementation with your favourite UI libraries: react, angular, vue.js, jquery, d3, or vanilla javascript. You can also integrate multiple libraries into your app at the same time, as what I did in this demo app.

You can play with it at: https://bhou.github.io/ui-architecture-demo/

The following screenshot is our final demo app: a counter app with 5 different implementations in it (vanilla javascript, react, angular, vue.js, and d3 (showing counter history with line chart)), and they share the same business logic (with redux pattern, but not using redux library).

The source code can be found at: https://github.com/bhou/ui-architecture-demo

Here is the architecture diagram, generated directly from the application with collar.js and collar-dev-server:

architecture diagram generated by collar.js

Engineering in javascript world

I am a traditional computer science background person. My education and experience make me always design the software first, and pick the right tool/library to build it.

It’s been years that I worked with javascript for both front-end and back-end applications, and I don’t know from which time, the javascript world has a totally different engineering process — starting from a framework/library, and adapt the code to that framework style.

Let’s take front-end for example, you can find tens of frameworks or libraries to help you build your applications: React, Angular 2 (and 1), Vue.js, Flux, Redux, Redux-router. You can also find lots of boilerplate projects in github (for example, the famous create-react-app project made by facebook) to help you start your project with a proposed project structure. Once you dig into any of these libraries, you will find they have totally different mindsets and styles to make things done. If you adapt your code to one of them, it almost means that saying goodbye to others. (.jsx for react, .vue for vue.js, .ts for angular …)

The worst comes with flux and redux, they are just two simple design patterns, but people starts to treat them as a framework, a new technology, and there comes thousands of how-to articles to teach you how to make apps with them.

I even saw a new job title: react/redux engineer recently.

In my personal opinion, this is too crazy. Shouldn’t we just forget about all these libraries and focus on designing the architecture that adapts to the business need and then pick one of these libraries that fits well to our design?

Here comes this article: building a counter app with software architecture design first, and then integrate different libraries into it.

Let’s start from the architecture

Although we will not think about using these libraries from very beginning, the great ideas behind of these frameworks/libraries can help us to design the architecture. In this article, I will borrow the idea of Flux and Redux. Flux gives us a good way to model the data flow (unidirectional data flow), and redux shows us a good pattern to manage the application state. Our architecture contains two top level components: view and store.

The following diagram shows the architecture: (it is directly generated by collar.js and collar-dev-server)

Our view has two responsibilities:

  1. render the UI with data
  2. accept user interaction and send action

The arrow line points to view component shows that the store can push data to the view for rendering, the arrow line points from view to store indicates the action emitted by the view (usually triggered by a user interaction).

The store handles the business logic (by handling the action emitted by the view, and push new state to view for rendering when state changes)

In the first part of this article, I will focus on design and implement the view system. We will cover the store component in the part 2.

View Abstraction

Now we have our design, let’s build an abstraction layer to describe it:

For the view, we can create an ES6 class to represent the interface:

It has 4 methods for the subclass to override:

  1. render(state): takes an object as argument to render the view, you need to add UI event listeners in this method and call this.send in the listeners to emit actions
  2. update(state): update the view, this is useful to refresh part of your view
  3. send(action): this methods is used to send an action to your view controller, action is simply a javascript object
  4. addActionHandler(actionType, handler): register a handler for a special type of action

This abstraction is trying to have a view interface independent to any controller implementation. You can use the redux-like store component (in our design) as the controller or you can plug-in the business logic directly to the view with addActionHandler method.

To build your application with this view abstraction, you just need to implement a subclass, implement the render and update method, and use send method to emit action. The following code demonstrates how to build a counter view with vanilla javascript:

To use it in your application, simply put the following code:

We update the view in action handler by calling counterView.update(state)

Your app looks like this:

You are a react fan?

If you want to use react for the view, it is very easy, just subclass it with react implementation, here is an example:

We pass the view instance (this) to the “view” property, so that our react component can use it to emit actions (by calling send method)

To integrate this view into your application, you can do the same thing as we did for the vanilla javascript version, by replacing vanillaCounterView with reactCounterView.

Make the view component data flow visualisable

To go further, you can implement the abstract view class with collar.js and visualise your view’s data flow with collar dev tool.

Collar.js is message driven. For our view component, it handles two messages: render and update . Each takes the state as message body. We build a CollarView class to represent it.

In the constructor, we build the following data flow by creating each node and wiring them up together:

data flow for view component

We have a different implementation in update and render methods: Instead of leaving the implementation of these two method to subclass, we send a message (render or update) through the input node (the top left one), so that it will trigger the corresponding pipeline (render or update).

Two new methods are added: setRenderer and setUpdater . A renderer or an updater is a function with the following signature:

function renderer(state, done)
function updater(state, done)

They take a state object as the first argument, and call done callback when update/render is done. Renderer and updater is called in the render the view node and update the view node.

A sensor node is created to help the view send actions. In the send method, we simply delegate the action-delivery job to sensor. When you add action handler to the view, an action pipeline is created and connected to sensor node.

We can rewrite our vanilla javascript implementation with the CollarView class:

And create our application with it:

Now you can see the data flow from collar dev server :

The top is the data flow (pass state to view) from controller to the view in order do the rendering and updating. The bottom is the action flow, starts from the view (sensor), and flows to controller. (in this example, the controller is integrated in the view, as the INCREMENT and DECREMENT branches)

Conclusion

In this article, we designed a view interface, and provided two abstract view classes, one based on simple event emitter (≈30 lines of code); another based on collar.js (≈80 lines of code) to give us the possibility to visualise the data flow.

The design of this view interface makes it independent to any controller implementation. You can even plug-in your business logic in the view with action handlers.

Based on the abstract view, we implemented two counters, one based on vanilla javascript, another based on react.

In next part, we will design the store component, and provide a redux like API to help you manage your application state.