Angular services and useable patterns

AngularJS is a relatively new javascript framework that is taking the web application development world by storm. It boasts some incredible architecture and supporting components that change the way you think about building applications. One of those components is known as 'services'.

Services provide a 'hooked-in' way to perform data-centric tasks. By hooked-in, I mean that they are hooked into Angular, which means that they are expose-able through dependency injection and chainable from your app's root module.

This article assumes that you have some experience with Angular and know how to add a service to your Angular app. If that sounds like something you may have not done yet, check out these resources to help you get started:

John Lindquist's video series, Egghead:
http://egghead.io

The AngularJS Wiki:
https://github.com/angular/angular.js/wiki

Service Instantiation

Let's say you've just created an empty service. To demonstrate how services are started, add this line:

angular.module('myApp').service('myEmptyService', function() {
  console.log(this);
});

Once you've created your service and added a simple log line, inject your service into a controller:

// You don't even have to reference it within the controller's closure, simply pass
// it in as an argument for now.
angular.module('myApp').controller('MyFirstCtrl', function(myEmptyService) {});

Upon refreshing the page (that uses the above controller) you'll notice the console has some new information for us:

Constructor {}

This right here tells us quite a bit about our new service. What's the only javascript operation that can result in a constructor function? That's right, calling a function with 'new'.

*It's important to note that the 'Constructor' object returned by the service is NOT the same as the reserved keyword 'constructor'. This here is quite literally Angular returning an instance of a function named 'Constructor' that was called with 'new'. When you call a function with 'new' it applies a new scope (constructor instance) to the function, granting the ability to use scope operators like 'this' inside of its closure/attached methods. See: http://jsfiddle.net/javascriptenlightenment/FKdsp

So we know that services are called with 'new', but what does that mean? Let's do some research!

Create another controller and inject the same service again:

angular.module('myApp').controller('MySecondCtrl', function(myService) {});

After doing this, give your app a full refresh and navigate between the views that use those controllers. What happens in the console? You should only see 'Constructor {}' output to the console once (per full page refresh, if your app is not using Angular's single page routing, Angular itself will be reloaded with every URL change, meaning you will see 'Constructor {}' every time you move between views)!

So, no matter how many times you inject your service, it is only given a new instance once. This is know as being 'Singleton'.

Pattern One: Vanilla.

What I love about controllers/services in Angular, is that they provide you a blank canvas to build your application the way you see fit, while still being wrapped in Angular's architecture.

With this in mind, you are free to write your code as if developing in a blank file. The thing to always remember is, depending on how you design your service (see subsequent patterns), the code within it will run only once per full-page refresh.

angular.module('myApp').service('myService', function() {
  function foo() {
   console.log('this will run once')
   bar();
   baz();
  }
 
  function bar() {
   console.log('this will run once')
   baz();
  }
 
  function baz() {
   console.log('this will run twice')
  }
 
  foo(); // init
});

Using a singleton class doesn't prevent you from executing contained code more than once, it means that there will only be one instance of the class. We'll talk more about what that means later.

Pattern Two: Reusable components.

The above service will perform greatly if we only ever need the contained code to be fulfilled once. Sometimes, that's exactly what you need a service to do, and that's totally fine. If we exercise some creativity, however, we can utilize services to a much greater potential.

With the above service, we could inject it into any controller across our app and it wouldn't give us anything special. If we alter our design by just a little, multiple injection points can actually matter!

Let's say we have created a service to manage a user's identity. In certain controllers/directive controllers, we may want to show alternate markup based on the current user's role. And if we have multiple controllers that have different scope and DOM concerns, we'll need to make sure we can show the appropriate DOM from multiple injection points.

What our service needs is an API.

angular.module('myApp').service('identity', ['$http', function($http) {
  var currentUser = null;
 
  function getRole() {
   var role = 'guest';
   if(currentUser) {
    role = currentUser.role;
   }
   return role;
  };
 
  function login(username, password) {
    return $http({method:'POST', url: 'our/login/url', data: {username: username, password: password}}).success(function(data) {
      currentUser = data.user;
    });
  };
 
   // The API
  return {
    login: login,
    getRole: getRole
  }
}]);
 
// User Controller
angular.module('myModule').controller('UserCtrl', function($scope, identity) {
// Before login will return as 'guest'
 $scope.currentUserRole = identity.getRole();
 
 $scope.login = function(username, password) {
  identity.login(username, password).then(function(data) {
   // Will resolve to user
   $scope.currentUserRole = identity.getRole();
  });
 }
});

So that means anywhere we inject 'identity' we can then call 'identity.getRole()' and use it to dictate application behavior. So while there is only one identity instance, we can execute code with in it multiple times through our interface.

Pattern Three: Hybrid/Facade

Our service has made some strides in reusability through our custom interface. What about use cases where you *do* want to create injectable code that *does* create a new instance with each injection/invocation?

There are many discussions on that point of Services vs Factories that I encourage you to read. This article will remained focused on Services.

Be it that Services are singleton in nature it might feel restrictive when thinking of implementation use cases. Not wanting to miss out on utilizing Angular's dependency injection system, however, has us thinking of some possibilities.

A common task when building a web application is to wrap your request(XHR) code in a convenience providing API to reduce the occurrence of the same URL (amongst other repetitive data) being written over and over again.

A situation like this calls for some static information that applies to every request and some that needs to be different for every request. We can take advantage of the service's singleton nature while adding a (pseudo) sub-class that returns a new instance to accomplish this goal.

// Request service
angular.module('myApp').service('request', ['$http', function($http) {
  var Request = function() {
    // Convenience helpers
    this.endpoints = {
       user: 'user',
       login: 'user/login'
    };
 
    this.apiBase = 'http://myserver.com/api/';
 
    this.make = function(options) {
       var url = this.apiBase;
 
      // resolve URL
      if(this.endpoints.hasOwnProperty(options.endpoint)) {
        url += this.endpoint;
      }
 
      // return a new request object
      return new HTTP(url, options);
    }
    return this;
  };
 
  // Our XHR object. This one gets a new instance with every request.
  Var HTTP = function(url, opts) {
    return $http({method: opts.method, url: url, data: opts.data});
  };
 
  // This only gets called once
  return new Request();
}])
 
// Arbitrary controller
angular.module('myApp').controller('myController', function($scope, request) {
 // use the request service
 $scope.login = function(username, password) {
   request.make({method: 'POST', endpoint: 'user', data: {username: username, password: password}}).then(function(data) {
     // custom login behavior
   });
 };
})

So here we begin by creating an instance of the Request function. This instance only gets created once. It holds data that doesn't change like the base URL of our API and common endpoints. It offers the 'make' function as its interface.

When the 'make' function is called, it constructs the appropriate URL and then starts the actual HTTP request by calling 'new' on our request helper. This way we can keep static data static while accommodating dynamic data.

For a demo of the final pattern, see here: http://plnkr.co/edit/QDw5TcJo840ckpJVCEd3?p=preview

Hopefully this article got some wheels turning with what can be done with Angular services and intuitive javascript!

Thanks for reading!

Filed under:

Comments

There is an error in your last snippet, this.endpoint is undefined. I think you should use instead:

url += this.endpoints[options.endpoint];

Thank you.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.

Ready for transformation?