Services

Up until now, we’ve only concerned ourselves with how the view is tied to $scope and how the controller manages the data. For memory and performance purposes, controllers are instantiated only when they are needed and discarded when they are not. That means that every time we switch a route or reload a view, the current controller gets cleaned up by Angular.

Services provide a method for us to keep data around for the lifetime of the app and communicate across controllers in a consistent manner.

Services are singleton objects that are instantiated only once per app (by the $injector) and lazy-loaded (created only when necessary). They provide an interface to keep together those methods that relate to a specific function.

$http, for instance, is an example of an AngularJS service. It provides low-level access to the browser’s XMLHttpRequest object. Rather than needing to dirty the application with low-level calls to the XMLHttpRequest object, we can simply interact with the $http API.

// Example service that holds on to the
// current_user for the lifetime of the app
angular.module('myApp', [])
.factory('UserService', function($http) {
  var current_user;
    return {
    getCurrentUser: function() {
      return current_user;
    },
    setCurrentUser: function(user) {
      current_user = user;
    }
  }
});

Angular comes with several built-in services with which we’ll interact consistently. It will also be useful to make our own services for any decently complex application.

AngularJS makes it very easy to create our own services: All we need to do is register the service. Once a service is registered, the Angular compiler can reference it and load it as a dependency for runtime use. The name registry makes it easy to isolate application dependencies for mocks and stubbing in our tests.

Registering a Service

There are several ways to create and register a service with the $injector; we’ll explore them later in this chapter.

The most common and flexible way to create a service uses the angular.module API factory:

angular.module('myApp.services', [])
  .factory('githubService', function() {
    var serviceInstance = {};
    // Our first service
    return serviceInstance;
  });

Although this githubService doesn’t do anything very interesting, it is now registered with the AngularJS app using the name githubService as its name.

This service factory function is responsible for generating a single object or function that becomes this service, which will exist for the lifetime of the app. When our Angular app loads the service, the service will execute this function and hold on to the returned value as the singleton service object.

The service factory function can be either a function or an array, just like the way we create controllers:

// Creating the factory through using the 
// bracket notation
angular.module('myApp.services', [])
  .factory('githubService', [function($http) {
  }]);

For instance, this githubService requires access to the $http service, so we’ll list the $http service as a dependency for Angular to inject into the function.

angular.module('myApp.services', [])
  .factory('githubService', function($http) {
      // Our serviceInstance now has access to
      // the $http service in it's function definition
      var serviceInstance = {};
      return serviceInstance;
  });

Now, anywhere that we need to access the GitHub API, we no longer need to call it through $http; we can call the githubService instead and let it handle the complexities of dealing with the remote service.

The GitHub API exposes the activity stream for the user on GitHub (this stream is simply a list of recent events that a user has logged on GitHub). In our service, we can create a method that accesses this API and exposes the resulting set to our API.

To expose a method on our service, we can place it as an attribute on the service object.

angular.module('myApp.services', [])
  .factory('githubService', function($http) {
    var githubUrl = 'https://api.github.com';
      var runUserRequest = function(username, path) {
      // Return the promise from the $http service
      // that calls the Github API using JSONP
      return $http({
        method: 'JSONP',
        url: githubUrl + '/users/' + 
              username + '/' + 
              path + '?callback=JSON_CALLBACK'
      });
    }
    // Return the service object with a single function
    //  events
    return {
      events: function(username) { 
        return runUserRequest(username, 'events'); 
      }
    };
  });

The githubService contains a single method that the components in our application can call.

Using Services

To use a service, we need to identify it as a dependency for the component where we’re using it: a controller, a directive, a filter, or another service. At run time, Angular will take care of instantiating it and resolving dependencies like normal.

To inject the service in the controller, we pass the name as an argument to the controller function. With the dependency listed in the controller, we can execute any of the methods we define on the service object.

angular.module('myApp', ['myApp.services'])
.controller('ServiceController',
    function($scope, githubService) {
      // We can call the events function 
      // on the object
      $scope.events = 
        githubService.events('auser');
});

With the new githubService injected into our ServiceController, it is now available for use just like any other service.

Let’s set up our example flow to call the GitHub API for a GitHub username that we define in our view. Just as we saw in the data binding section, we’ll bind the username property to the view.

<div ng-controller="ServiceController">
  <label for="username">
    Type in a GitHub username
  </label>
  <input type="text" 
        ng-model="username" 
        placeholder="Enter a GitHub username" />
  <ul>
    <li ng-repeat="event in events">
      <!-- 
        event.actor and event.repo are returned
        by the github API. To view the raw
        API, uncomment the next line:
      -->
      <!-- {{ event | json }} -->
      {{ event.actor.login }} {{ event.repo.name }}
    </li>
  </ul>
</div>

Now we can watch for the $scope.username property to react to how we’ve changed the view, based on our bi-directional data binding.

.controller('ServiceController',
    function($scope, githubService) {
    // Watch for changes on the username property.
    // If there is a change, run the function
    $scope.$watch('username', function(newUsername) {
      // uses the $http service to call the 
      // GitHub API and returns the resulting promise
      githubService.events(newUsername)
        .success(function(data, status, headers) {
          // the success function wraps 
          // the response in data
          // so we need to call data.data to 
          // fetch the raw data
          $scope.events = data.data;
        })
    });
});

Since we are returning the $http promise, we can call the .success method on the return as though we are calling $http directly.

Using $watch in the controller as above is not recommended. We’re using this example only for simplicity’s sake. In production, we would wrap this functionality into a directive and set the $watch function there instead.

 
This page is a preview of ng-book.
Get the rest of this chapter plus 600 pages of the best Angular content on the web.

 

Ready to master AngularJS?

  • What if you could master the entire framework – with solid foundations – in less time without beating your head against a wall? Imagine how quickly you could work if you knew the best practices and the best tools?
  • Stop wasting your time searching and have everything you need to be productive in one, well-organized place, with complete examples to get your project up without needing to resort to endless hours of research.
  • You will learn what you need to know to work professionally with ng-book: The Complete Book on AngularJS or get your money back.
Get it now