Saturday, March 21, 2015

Keeping Angular controllers complexity and size under control by using plain JavaScript widgets

Another problem I found while working on a legacy Angular application last year, was huge controllers with several hundreds of lines.

Most of those controllers contained a lot of procedural code and were doing too much. This made them very hard to test.

In the new Angular application we're currently working on, we're refactoring very aggressively to keep the size of our controllers small.

Apart from the strategy I talked about in a previous post, another technique that is giving us good results is extracting as much logic and state as possible from the controllers by moving related state and logic into small plain JavaScript objects that we call widgets.

This has had a huge impact in the testability of the code and helped us to keep the controllers size and complexity small.

Now I'll show you the results of one of this refactors, so that you can see its impact in a controller.

This is the first working version of a controller that is coordinating the behavior and content of a page showing details about a track. This page contains a map (using OpenLayers) with the polyline of the track on it, some statistics about the track and several charts (plotted using flot library)) to present some data from the track. It's possible to interact with these charts by changing the magnitude represented in their x axis (it can be distance or time):

Notice how there are a lot of functions in the scope which are managing the state of the xAxis variable and getting information about the charts (x and y axis units, selected magnitude for x axis).

We had also this directive for the charts:

which was being passed the selected x axis magnitude and the charts data.

We got to this version of the code after several design changes and quick spikes. Once we were happy with the UI behavior, it was time to stabilize it.

To do it we created a widget in an Angular factory which was a plain JavaScript object in charge of managing the state related to the charts (xAxis variable which was being read and mutated) and accessing the charts data (which were only being read): Notice how the widget is created by passing the track to the create method. This method initializes some values and returns the charts widget which has privileged access through a closure to several private functions and data.

We basically moved all the functions related to the charts that were before in the controller into the new widget and made some of them private.

After being moved to the widget, the logic controlling the charts became very easy to test using fake tracks:

After this change the controller was reduced to this:

Notice how all the chart-managing logic has disappeared from the controller. The only code related to the chart that remains is its creation.

We were also able to simplify the code of the directive by just passing it the new widget:

Notice how the widget has also absorbed the code that produced the points in the chart.

We have continued refactoring this controller and others in the same way. Making small refactors every time we detected duplication or discovered new concepts that could help us extract behavior from them.

This is the current code of the controller that we've been showing in this example:

Keeping Angular controllers complexity and size under control is a constant effort that, having seen in other projects the negative effects of huge controllers, I think it's totally worth it.

No comments:

Post a Comment