Adopting AngularJS in Existing Applications

In an ideal world, a team of developers tasked with rewriting an existing application in AngularJS would be able to take their time. They would be given the time, budget and resources necessary to make sure each developer understood the framework before attempting to implement it.

We don’t live in an ideal world, however, and projects never have unlimited time, budget or resources. As a result, developers new to Angular try to incorporate it and typically end up using it “halfway.” They wrap code in controllers and set up routes, but don’t utilize dependency injection or services. When it takes too long to write things in an Angular way, they fall back to what they already know, and use pure JavaScript or jQuery to accomplish their functional requirements. This leads to messy code that isn’t modular, testable or scalable.

In these situations, I often see the following problems in the code:

  • Multiple objects in a single file
  • Global variables
  • No use of Angular’s built-in services
  • Business logic in controllers
  • Repetitive code related to base objects, common variables, etc.
  • Duplicated code for controllers/services with similar functionality

Each of these issues will make developing an app more difficult as time goes on. The best solution is to remedy these problematic chunks of code as soon as possible. The good news is that you can refactor code like this incrementally, as there is time, without having to rewrite the entire application all at once.

Break up files

Every component should have its own folder, and every controller/service/directive should have its own JavaScript file. Further, it’s best to group your files by feature, rather than type.

For example, rather than organizing your files like this:

you want to organize your files like this:

The above example is not a strict guideline for how you should organize and name your folders, but you should group related items together, so that when you’re working on a feature, you don’t need to open multiple folders to get to related controllers, services, views, etc. Additionally, the naming convention above is just a suggestion. Whatever your team prefers should be fine, as long as you communicate a file-naming convention and stick with it. Consistency is key, and will keep your app maintainable and scalable in the long run.

Eliminate Global Variables

Global variables are fragile, because you have no way of knowing if they exist before attempting to use them in your services and controllers. To fix this, wrap any and all global variables in a service that you can then inject into your controllers as needed.

For example:

By wrapping moment in a service, you can use dependency injection to make sure you are grabbing the right moment object, and that it exists for you when you instantiate your controller. This also makes moment into a testable object, because you can mock it out before you instantiate the controller in your unit tests.

Don’t forget Angular’s Built-in Services

Often when developers are struggling with a new framework, they’ll fall back to what they already know, because that will take less time to implement. However, doing this when trying to use Angular just makes your code harder to maintain and test.

For example, I often see use of jQuery’s $.ajax() function to make an HTTP request, as opposed to using Angular’s $http service. This is sub-optimal, because it’s not testable, and it’s not making use of the tools Angular provides out of the box:

Again, this makes your code more testable, as you can now mock out these service dependencies properly.

Utilize Angular Values and Constants

Sometimes, you might need a global object to hold configuration variables, other constants, etc. However, storing these as global variables is fragile. A better solution is to use angular.value() or angular.constant() to return whatever variables you need. For example:

By using angular.value() in the above example, you can make your code more testable and encapsulated.

Extend Base Services/Base Controllers

Once the above steps have been taken, you can really look at your code and see where you might have duplication. In business applications, I often see situations where the same type of functionality is present in several services, or several controllers, with the only difference being the endpoint that is called, or the name of the object being acted upon.

This isn’t very clear as an abstract concept, so I’ll return to my pet store example. Let’s say I have two services, one for cats and one for dogs. Each service has a getList() function, which will get me a list of the animals of that type from the server. They will also each have a getInfo() function, which takes an animal’s ID as a parameter and returns data about that particular animal:

Looking at these services, the two are almost identical. The only differences seem to be that one uses the term ‘cat’ while the other uses the term ‘dog,’ and they call different endpoints (‘/cats’ vs. ‘/dogs’). You can abstract out that repetitive code by creating a base service and then extending it for each specific instance you need:

With the repetitive code now abstracted into a more generic service, you only need to test that functionality in one place, and then just make sure it was injected and called properly in the specific services that extend it.

This method is also useful for controllers that are very similar.

Takeaways

While incorporating AngularJS into an existing app can be a daunting process, particularly for developers new to the framework, there are steps you can take to incrementally make your code more “Angular” as your time and budget allows.

For further explanation, take a look at my talk from ng-vegas, which this post is based upon.

This post was originally published on EffectiveUI’s blog on May 14, 2015.