Post-MVC part 2: MVC and JavaScript

Intro

Last week we discussed what MVC is and how it was used to render application and websites from the server. But our application's user interfaces became too ambitious and our code became spaghetti. This week we will discuss MVC and JavaScript, and the birth of the Component.

MVC and JavaScript

Taking full control of the UI meant things had to be programmed on the browser side. Meaning that things had to be programmed in JavaScript.

This gave rise to the era of the browser based MVC controller frameworks. Backbone was the early pioneer showing us that we could straighten our jQuery Spaghetti. Angular and Ember improved upon Backbone by giving us automatic bindings, when the model changed the views were automatically updated.

Backbone, Ember, Angular are all MVC frameworks, which took the tried and true architectural pattern, and allowed us to make great applications.

And for a while everything was good.

Trouble in paradise

But there was trouble brewing in paradise.

MVC comes with a particular problem: it is very difficult to reason about what is going on in a larger application. Once you have multiple Controllers and Views which manipulate the same Model things get confusing. 

Imagine an application for buying and selling groceries. Which has a model for the 'Shopping chart' of what the user wants to order. The model for the shopping chart can be manipulated through various ways:
  1. Opening the shopping chart detail page and removing items.
  2. Opening a product detail page and pressing 'add to chart'
  3. Clicking on special banners giving personalized discounts.
Each of these three ways have their own View and Controller, but share the same Model of the shopping chart. If you were to create a graph of the relationships between these objects you will find that things get very complex.

Each new View and Controller adds ways in which the state of the model can be manipulated. Trying to mentally understand shopping chart's state and how it can be influenced becomes difficult if not impossible. If a bug triggers things to be added to the chart twice, where do we look? It could be in any of the three controllers and views that influence the shopping chart.

The 'shopping chart' example shows how a Model for a single entity can be difficult to manage. Imagine a situation were multiple entities can affect each other, the mental gymnastics you will need to perform to keep the whole picture in your head would be heroic.

To figure out how we can solve this problem we need to make a detour.

Abstracting Views

Consider what a view is. A view normally shows a bunch of widgets on the screen for the user to interact with. A button here, a table there and some input elements on the side. We can use them to manipulate and view the Model.

In our applications we almost never use the basic HTML widgets as is. We style them using CSS to change their appearance. We also give them new behaviors such as an auto-complete functionality. Sometimes we even create completely new Widgets such as a 'Maps' widget to show a geographical location.

Of course we abstract these widgets away into reusable code. In Angular we might make a bunch of directives. In Ember we would create Components. They have different names but they represent the same principle.

Our Views are basically build using regular HTML and these widget abstractions the various MVC frameworks provide.

The common term for these abstractions is called "Component".

The birth of the Component

A Component is like an island. It has nothing to do with the outside world, as such that it will not affect it. This means you can use the same Component multiple times on the same View without them interfering with each other.

Since a Component is isolated from the rest of the system, that means that if you want to interact with the Component you must send messages to it. But the same is true the other way around, if the Component wants to talk to the outside world he must send messages to the outside as well.

The great benefit of having isolate components is that they are easy to reason about, to understand how the component works you can study just the components source. If you know a components incoming messages and outgoing messages you understand how to use the  component.

There is a proposal to make Components available in the web natively called: "Web Components". The proposal is to make it possible for us to create truly isolated components with their own tags.

Study of a Google Maps Component in Polymer

Google Maps is a service by Google which shows geographical maps in the browser. Using Polymer, which is a framework made by Google to create web components, Google made a component for Google Maps:

To use the Component we write the following HTML:
<google-map fit-to-markers="" latitude="37.77493" longitude="-122.41942">
  <google-map-marker draggable="true" latitude="37.779" longitude="-122.3892" title="Go Giants!">
  </google-map-marker>
</google-map>
If you study the code snippet above it is easy to guess what the Component does. It creates a google-map widget centered on a particular longitude and latitude but is wide enough to fit all markers. Inside the map there is a marker on a coordinate which is draggable and has the title: 'Go Giants'.

The point is that a Component is very declarative in use. You don't write how you want something to get done, you write what you want the end result to be.

The Google Maps Component itself can be very complex. I imagine that it is certainly not trivial to implement the code behind it. However the usage of the component is not difficult at all, it has a very simple interface.

To communicate with the component that we want another latitude and longitude we simply change the attribute on the <google-map>'s element. The same goes for the title of the marker.

If the Component wants to communicate with us we must listen to the correct channel. A component in Polymer communicates through events. For example to listen to a click on the map:

var map = document.querySelector('google-map');
map.addEventListener('google-map-click', function(e) {
  alert('The user clicked on the Map!');
});
The channel in this case would be the 'google-map-clicked' string. With it we tell the Google Map Component that we are interested in  these type of events. The callback function lets us 'do our thing' when that event actually occurs. The Component however stays responsible for determining when the event takes place. The Component calls the outside world.

The concept of Components are implemented in various frameworks such as: ReactAngular 2.0Ember and Polymer. A Component's "code" will be different in each framework, but they  follow the same principles: isolation, declarative interfaces, and  explicit channels of communication.

A realization about Components

A Component has behaviors and a look and feel, it also has a state. For example: a button component can be enabled or disabled, a person component shows a certain person's details the person which is shown is the state.

Reading the above paragraph it might dawn upon you that a Component is in some ways a version of MVC, but on a much smaller level. A Component has behaviors which map to the Controller. A Component has a look and feel which maps to the View. A Component has some state which maps to 
the Model.

Can a Component do anything an normal MVC pattern can do? The answer is yes. If a Component can do anything a normal MVC pattern can do, why do we still use traditional MVC, why not go all in with Components. They are more declarative, they are easier to reason about because they live in isolation, they fit in one single file.

But what about the View that ties all Components together surely we need those? The answer is no. You can define Components in terms of  other Components, using them as building blocks to create new abstractions. This capability can completely replace the traditional View.

This realization was the beginning of the Post-MVC Age. Which is the topic of next week's post. In that post we will try to discover how an application that uses only Components works.