Avoiding huge controllers in AngularJS

JavaScript Object Oriented Programming to the rescue!

Controllers with too many responsibilities

One of the first challenges I faced using AngularJS was to develop and handle my models inside the AngularJS controllers. After reading the official AngularJS documentation, it wasn’t clear to me at all how to write them. The examples don’t show how to keep them organised to build large front-end (real) applications, because all the model data is declared inside controllers scope.

If a controller is in charge of handling a tiny model, with just a couple of variables, the example (check the invoice1.js file) in AngularJS documentation seems to be the right and easiest way to do it. But when our models start growing, having dozens of variables and business logic, things start to get messy, difficult to maintain and impossible to unit-test. So how could we avoid huge controllers?

The goal of this post is to describe how I managed to do it in a clean, object oriented and testable way. At first, I’ll talk a little bit about the problems I found following the AngularJS documentation examples. And then, I’ll introduce the proposed approach which allows us to:

– Write re-usable model objects

– Develop clean controllers which are just in charge of orchestrating other components

– Keep clean html partials, since they share scopes with controllers.

AngularJS traditional approach

Let’s suppose we need to develop a form, which is used to create a new instance of one of our models (ex. a Contact from our Agenda). To develop this form, it seems enough to create two model variables inside the controller, and when the user presses the save button, call the service to save the new entity.

Here’s the form controller code. Or you can check the complete example.

This is a simple controller, but it’s easy to see a potential problem as the controller has a method called getFullName(). If we need to display the contact name in another section of the agenda, we’ll be duplicating code (business logic).

Also, things get harder in our form’s controller when we have to: – Watch variables – Hide/Show UI elements depending on the model state – Validate form values (using client or server side validations) – Use custom AngularJS directives

Main Disadvantages:

  • Huge controller files (which means low cohesion) containing:
    • Model code
    • Business logic
    • Helper methods to use in the UI
    • Calls to services
    • Variable watches
  • You can’t reuse model-related code in other controllers.
  • It’s harder to unit test controllers than plain old JS objects. You have to inject/mock all the angular dependencies the controller uses.

Proposed Approach: use OOP in AngularJS

Even though this is not a revolutionary approach, when I started to work with AngularJS I didn’t realize the importance of having well defined models. After reading all the official documentation, it didn’t seem like an important thing to have in mind.

The first pages I developed were simple enough to follow the AngularJS examples, but when I started developing a big form controller, after some time passed and new requirements were defined, the code got really ugly. So, I had to refactor it, to keep it well organized, maintainable, and easy to test.

Models and Controllers

I believe the ideal scenario is to keep our controllers as clean and simple as we canThe basic idea is to have an object which represents our model entity. Controllers should JUST be in charge of:

  • Instantiating model instance/s.
  • Calling services to perform back-end operations (with the model instance as an argument).
  • Watching model fields and triggering actions depending on their changes.
  • Calling model methods.

Partials

At the same time, this approach also helps us to keep our partials (or HTML templates) clean. Through the scope, partials can use model variables or call model methods to: – Bind to model variables (line 4) – Check or unckeck input elements calling a method (line 7) – Hide or show html elements (line 9) – Change validation rules based on the model state (line 14)

Declaring classes in JavaScript

As many of you know, there are several ways to work with classes and objects in JavaScript. If you don’t know about it, it can be worth checking out some information. Since I’m a huge fan of UnderscoreJS (I think it’s one of those  MUST have libraries for any serious JavaScript application), I’ll use it to show an example of how to develop model classes using this library.

For example let’s see the Contact model class for our Agenda example:

  • At first we define the class constructor using some default values.
  • Then we define all the class state methods and helpers.
  • Return the class definition.

Advantages: – Model methods can be re-used in different controllers (e.g. getFullName() method). – JS objects are easy to unit test, since there is no need to add or mock dependencies. Business logic is the core of our application, so model classes are the “prime candidates for unit testing”. – The business logic related to contacts is not scattered among different controllers.

Light and nice controllers

The form controller code to create and edit contacts is small and simple:

Conclusion: more organised AngularJS applications

A nice to have would be a JavaScript abstract class which would have all the common methods for defining model classes. This way, we would not duplicate the code.

I’ve created a mini AngularJS application (Agenda) to show how to use model classes with controllers, partials and services. This is the link to the github repository: https://github.com/matiasalvarez87/angular-agenda. You can also check out the live demo.

IMHO, I believe this approach allows us to have more organised AngularJS applications. Each kind of component is just in charge of a specific task. Right now, I’m using this approach to build a large AngularJS application and so far I haven’t had complex issues with it.

Please feel free to comment, share, ask or criticize. I’d like to hear thoughts about this topic and alternative ways to resolve the same problem.