Hi folks, as part of  series for migrating from AngularJS 1.x to AngularJS 2, we are back with the next step trying to get ready to migrate our AngularJS 1.x Factories/Services to AngularJS2 Service. If you missed our previous post on controllers, check it out at Migrating to AngularJS 2 controllers

This article aims to guide you towards changing your AngularJS 1.x factories or services today to make it easier to migrate to AngularJS 2 services.

An AngularJS 1.x factory

Lets say we needed a service named BookService which will allow us to call getBooks to fetch a list of books from the server and also store them in cachedBooks to handle future requests. If there are cachedBooks,  the api will return it as a promise and returning the book list from http request otherwise.

Let’s say we created this service using the AngularJS factory pattern as below:

app.factory('BookService', ['$http', '$q', function($http, $q){
  var cachedBooks;
  return {
    getBooks: function(){
      if (cachedBooks) {
        return $q.when(cachedBooks);
      }
      return $http.get('/books').then(function(response){
        cachedBooks = response.data.books;
        return cachedBooks;
      })
    }
  }
}]);

Steps to Migrate

There are multiple steps we can take successively to make it easier to migrate to AngularJS 2 services, whenever they become ready for primetime. You can choose to follow one, many or all of them as you see the need to. Remember, each step takes you one step closer to doing less work later!

Use .service instead of .factory

We can start making the migration to AngularJS 2 Service easier by changing the BookService from a factory pattern to a service pattern.

The factory pattern in AngularJS 1.x expects you to return an API object. When you use the AngularJS service pattern, it expects you to provide a JavaScript class. In such a case, you would have to specify each property of your API to be instance variables, set using the this keyword or through the use of prototype.

In our example, we will go one step ahead and use a self/vm pattern as suggested in John Papa’s style guide instead of this pattern. This is both from a discoverability stand point as well as a defense against unexpected bugs. The self/vm pattern makes it easier to quickly find what the API for the service is, and the this keyword is contextual and the context may change when used inside a function in Service.

So rewriting the above factory, our AngularJS 1.x Service would look as below:

app.service('BookService', ['$http', '$q', function($http, $q){
  var self = this;
  var cachedBooks;
  self.getBooks = function(){
    if (cachedBooks) {
      return $q.when(cachedBooks);
    }
    return $http.get('/books').then(function(response){
      cachedBooks = response.data.books;
      return cachedBooks;
    })
  }
}]);

Define the Service as a Class

Next, let’s extract the service constructor function to a class object BookService. This is again based on the suggestion from John Papa’s style guide.

Using classes or a named function makes code more readable, easier to debug and reduces the amount of nested callback code.

This change is a simple process and after the changes, our AngularJS 1.x service would look like below:

app.service('BookService', ['$http', '$q', BookService]);
function BookService($http, $q){
  var self = this;
  var cachedBooks;
  self.getBooks = function(){
    if (cachedBooks) {
      return $q.when(cachedBooks);
    }
    return $http.get('/books').then(function(response){
      cachedBooks = response.data.books;
      return cachedBooks;
    })
  }
}

Use ES6

AngularJS 2.x heavily leverages ES6 syntax to make the code more readable and easier to understand. We can optionally start using ES6 today, and use a transpiler to convert it to ES5 and run the code. This gives us the benefit of ES6 today, as well as getting our code one step closer to AngularJS 2.

Now, let’s rewrite the above service as an ES6 class. Instead of defining our classes as a function, we use the ES6 class keyword. Within the context of  this class, we have the ability to define a constructor. Also, any instance variables and methods can now be defined inside the class directly without having to use the this keyword or the prototype keyword. For more information on ES6 classes, take a look at this git repo on ES6 features.

So now after changing the BookService constructor function to a BookService class, our AngularJS 1.x Service looks as below. We are not showing the transpiler logic that would be needed to get it to work here, you can use something like this.

app.service('BookService', ['$http', '$q', BookService]);
class BookService {
  $http, $q, cachedBooks;
  constructor($http, $q) {
    this.$http = $http;
    this.$q = $q
  }
  getBooks() {
    if (this.cachedBooks) {
      return this.$q.when(this.cachedBooks);
    }
    return this.$http.get('/books').then(function(response){
      this.cachedBooks = response.data.books;
      return this.cachedBooks;
    })
  }
}

AngularJS 2 Service

At this point, we are done with what we could do within the context of AngularJS 1.x to get our code ready for the migration to AngularJS 2.0.

Now let us took at how an AngularJS 2 service might look, when we start using it:

import {HttpService, Promise}  from '../Angular/Angular2';
export class BookService{
  $http, $q, cachedBooks;
  constructor($http: HttpService, $q: Promise) {
    this.$http = $http;
    this.$q = $q
  }
  getBooks() {
    if (this.cachedBooks) {
      return this.$q.when(this.cachedBooks);
    }
    return this.$http.get('/books').then(function(data){
      this.cachedBooks = data.books;
      return this.cachedBooks;
    })
  }
}

You would notice that it is almost exactly the same as the AngularJS 1.x service we were left with.

So, let’s quickly list out some differences between AngularJS 1.x Service and AngularJS 2 Service

  • We don’t need to explicitly specify the class as a service using the angular.service function anymore, which we used to do for AnguarJS 1.x services.
  • The dependency injection is now type based, like, ‘HttpService’ is injected based on type and not the name based ‘$http’. This is optional, but without it you would have to add annotations or hints to let AngularJS figure out what the dependencies are.
  • The import statement is a ES6 feature which can be even used in AngularJS 1.x Services.

So with this, you services would also be ready for migration to AngularJS 2.

We will continue the migration series next with an article on Directives.

Hope, you find this migration series interesting.

Have any queries/comments/feedback?? Do leave a reply below and we will try to make this series more interesting.

CAUTION: This syntax is based on the proposal made in ng-conf 2015 and may be changed in future. Please refer to the AngularJS 2 home page for latest updates.

Parth MistryMigrating to AngularJS 2 Services