If you have an AngularJS 1.x application and intend to eventually upgrade to Angular 2, there are many refactoring and improvements you can take that will easy the process. Even if you are unsure, or do not intend to migrate at all, these strategies and patterns will result in more maintainable and cleaner applications.

This article reflects some of the leanings from starting a large AngularJS 1.x application one year ago, built with upgrade to Angular 2 in mind.

Typescript

Despite not being a requirement to use Angular 2, Typescript plays an important role in the new version of the framework. In the project mentioned above Typescript was used from day one. It’s a fantastic tool which brings many of the benefits from statically typed languages to Javascript. The type definition files from DefinitelyTyped are invaluable as well. Overall, we got fewer runtime failures, better development experience due to the real syntax completion, refactoring capabilities, and decreased cognitive load as side effect of all the other advantages.

Some of the code samples shown below are in Typescript.

From ng-controller to component-directives and components

The following snippet shows the traditional way of writing AngularJS 1.x code.

AngularJS 1.3 introduced the “controller As”, allowing us to get rid of the $scope is most of the cases (exception being when one need to use the intrinsic methods of $scope such as .$on(), .$watch(), .$apply(), etc).

Being able to remove $scope whenever possible was a nice improvement.

We can go further and make our applications both more maintainable and Angular 2 friendly. Let’s make components.

We will no longer use controllers. We will only use angular.directive() or the new angular.component() from AngularJS 1.5 (which is just a wrapper around directives with nice defaults). To improve discoverability we use an Injectables object, reducing on hardcoded strings and taking advantage of code completion. In the router we will no longer use templateUrl, instead we specify the main component which holds the view directly in the template. Naturally the main component for each route will contain the children components. We will create a component-directive, using AngularJS 1.3–1.4. The remaining example in this article are in Typescript.

It seems like a lot of code for one simple greeting component, but in the context of the large application, when there are dozens, or possibly hundreds of components, we made our life simpler. Moreover, it’s nice to be able to type “Injectables.” and let code completion do the rest than memorizing a myriad of strings.

One very important point of our component-directive is the “scope: {}”, always use isolated scope. This is in line with Angular 2 and can save some headaches latter, since the will not exist bleeding scopes.

To further improve, with AngularJS 1.5 we can make use of the angular.component() syntax to clean up some boiler plate.

This is very readable and easy to upgrade to Angular 2. Notice that because we can no longer use AngularJS’s dependency injection mechanism with angular.component(), in the templateUrl AppUrls is now a plain object ( or Typescript class with static properties).

Component communication

Communication between components is what gives life to an application. The Angular 2 documentation has a very good guide on the topic. Let’s look at an example of passing data from parent to child components and making the parent listen to child component events with Angular 1.x.

JSBin Demo

The interesting bit is in the binding of the todoItem component, where the todo is passed from the parent and a delete event is sent up to the parent. If you need communication between components that are nested more than two levels deep, consider using a service instead.

$scope and $rootScope

To make upgrading to Angular 2 simpler you should limit, or completely remove if possible, the usage of $scope and $rootScope. These are not available in the newer version of the framework. If you are using these to listen and emit/broadcast events, consider using service events to achieve the same effect. As an example, suppose that a “onScanData” event that is broadcast using $rootScope. To refactor this, we create a service and use a simple (reusable and easy to upgrade) event class to create a onScanData$ property in the service. A component would then inject the scanner service and subscribe to the event.

Eliminating $scope.$watch() is also recommended. For example the $onChanges(changesObj) lifecycle hook in AngularJS 1.5 can help in this task. Javascript getters and setters can also be useful in this context.

Component Lifecycle hooks in AngularJS 1.5

With the AngularJS 1.5 a number of convenient component lifecycle hooks was introduced, namely $onInit(), $onChanges(changesObj), $onDestroy(), $postLink(). Even though it was possible to tap into directives/components lifecycle before, these additions make the process simpler an map well with the Angular 2 component lifecycle hooks.

Component Router for AngularJS 1.5

Angular 1.5 introduced a new component router that defines routes in the same way as Angular 2, letting us take advantage of the components we defined above, and providing a simple way of structuring our applications as a hierarchy of components. Instead of

$routeProvider

.when(‘/greeting’, { template: ‘<greeting></greeting>’ })

we have

By now, some will be thinking that this looks very much like writing React with AngularJS. An that is somewhat true. And that is Ok. We get the best of both worlds.

To summarize, here is a list of points to improve maintainability of your AngularJS 1.x applications and make the transition to Angular 2 smoother: