Angular JS–Part 13, Services

Introduction

In this post I am discussing services and how they are constructed and tested. The list of earlier posts in this series about Angular JS can be found here.

What is a service?

In Angular a service is a function or an object and is used to share data and/or behavior across controllers, directives, filters, other services etc. A service is treated as a singleton, that is there is only one instance of a specific service available during the whole lifetime of the Angular application. This is different from e.g. a controller of which many instances can be created.

How to create a service

There are different ways how we can define a service in Angular JS.

The service recipe

The easiest way is to use the Service recipe of an angular module.

var app = angular.module('app', []);
app.service('some-service', function(){…});
In the above snippet the second parameter of the service function is the service constructor function where we would implement the behavior of the service.

Once defined this service can be used in any place, e.g. a controller through dependency injection

app.controller('some-controller', ['$scope', 'some-service'], function(scope, service){
    …
}]);

A service can of course also have external dependencies similar to a controller. We can inject those dependencies accordingly. Here our service uses the $location service of Angular JS

var app = angular.module('app', []);
app.service('some-service', ['$location', function($location){
    …
}]);

Here is a simple sample of a service that can calculate the area and the circumference of a circle give the radius. This service is not dependent on any other service.

image

On line 4 we define some data, in this case the value of PI. Then we have a function area defined on line 6 to 8 and a function circumference on line 10 to 12.

Testing the service

When doing TDD we need of course to know how we can test a service. This is straight forward. First we need to setup the context

describe('testing the circle-service', function(){
    …
});

Then we make sure that the Angular $injector is fully configured by calling the module function defined on window. module is a shortcut for angular.mock.module which in turn is defined in the angular-mocks.js library that we use during testing.

  beforeEach(module(‘app’));

The module function parses the whole Angular module we defined and registers all controllers, services, etc. It sets up also mocks for some of the Angular services like $httpBackend, $timeout, $browser etc.

Next we use the $injector to resolve the service

var service;
beforeEach(inject(function($injector){
    service = $injector.get('circle-service');
}));

Finally we can formulate our expectations, first for the value of PI

var pi = 3.141528;
it('should return the value pi', function(){
    expect(service.pi).toBe(pi);
});

and then for the two functions calculating area and circumference

it('should calculate the circumference', function(){
    expect(service.circumference(0)).toBe(0);
    expect(service.circumference(1)).toBe(2*pi);
    expect(service.circumference(2)).toBe(4*pi);
});

it('should calculate the area', function(){
    expect(service.area(0)).toBe(0);
    expect(service.area(1)).toBe(pi);
    expect(service.area(2)).toBe(4*pi);
    expect(service.area(3)).toBe(9*pi);
});

The factory recipe

Instead of defining the service directly using the Service recipe of the Angular module we can also use the Factory recipe and define a factory function which will create the service on request.

This is slightly more complicated but offers also some more fine grain control. To do this we use the factory function of the Angular module

app.factory('some-service', function(){…});

The purpose of the service factory function is to generate the single object, or function, that represents the service to the rest of the application.

Returning an object as a service

Assuming we want to return an object as our service we can do so

app.factory('some-service', function(){
    var obj = {
        foo: function(…){},
        bar: function(…){},
        baz: 'some value'
    };
    return obj;
});

here we have defined an object with 3 properties of which two are functions and one is just data.

Let’s provide a concrete sample. I have an interest service that can calculate the accrued interest given an amount, a yearly interest rate and a period in years. The service can also calculate the monthly rate I have to pay given a loan amount, a yearly interest rate and a payoff duration in years.

image

How can we test this service? Well when testing it really doesn’t make a difference whether the service is resolved via a factory function or directly. Thus the tests look very similar to what we saw earlier in this section

image

Please note that on line 2 I use the explicit angular.mock.module notation instead of just module to show that they are indeed equivalent. The latter is just an alias or shortcut defined in the angular-mocks.js library.

Returning a function as service

If on the other hand we want our service to be just a simple function we can do so like this

app.factory('some-service', function(){
    var fn = function(…){ return …; }
    return fn;
});

Here is a simple sample of such a service. The service just sums an array of numbers and returns this value

clip_image001

Once again testing this service is straight forward

clip_image001[5]

The only difference regarding the other tests is that here I skipped the creation of a beforeEach because I really only have one single it function and thus I can do all the setup there.

The provider recipe

The Angular documentation states the following

… Each web application you build is composed of objects that collaborate to get stuff done. These objects need to be instantiated and wired together for the app to work. In Angular apps most of these objects are instantiated and wired together automatically by the injector service. …

The injector needs to know how to create these objects. You tell it by registering a “recipe” for creating your object with the injector. There are five recipe types. The most verbose, but also the most comprehensive one is a Provider recipe. The remaining four recipe types — Value, Factory, Service and Constant — are just syntactic sugar on top of a provider recipe. …

So far we have discussed the Service and Factory recipe. The Value and Constant recipe are discussed in detail here. The last recipe which is the Provider recipe is fairly complex and rarely used. The Angular team recommends that we only use the Provider recipe if we need to expose an API for application-wide configuration that must be made (to the service) before the application starts. A possible scenario is a reusable service that is used in different modules (or applications/SPAs) and needs to vary slightly between those usages.

Let’s give a sample

image

On line 2 we use the provider function to register our provider (recipe). On line 6 through 11 we define our service which in this case is an object having two functions sum and average. Since both of these functions need to sum up the array of numbers passed we have defined a helper function sumFn on line 3 to 5 which does exactly that. This function is then used on line 7 and line 10. We also need to implement a $get function which is done on line 13 to 15. Here we just return the service.

How can we test this service?

image

The tests look very similar to the tests we implemented for the services defined through the Service and the Factory recipe.

Now, a provider is only interesting if I need to be able to configure it. In our case I want to be able to configure how the result of the various service function calls are rounded. Thus I introduce a precision parameter and a function through which we can configure this value.

We can change our provider as follows

clip_image001[7]

On line 3 to 6 we have introduced the configuration API with the function setPrecision. By default the precision is equal to 2. On line 15 and 20 we use the toFixed function to define the required precision of our return values.

Now we can use this configuration API during the configuration time of the Angular module to change the precision.

clip_image002

To make all work we also need to change line 2 of our recipe where we define the provider function and use a named function instead of the anonymous function. In our sample we need to call the function statisticsProvider because that is what we are referencing on line 29 above.

clip_image003

Conclusion

Services are important elements of a typical Angular JS application. Services allow us to share data and behavior across other objects like controllers. There exist 5 recipes how a service instance can be created; in this post we have described the Service, Factory and Provider recipes. In the context of Angular services are singletons. It easy and straight forward to test services.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Gabriel Schenker

Gabriel N. Schenker started his career as a physicist. Following his passion and interest in stars and the universe he chose to write his Ph.D. thesis in astrophysics. Soon after this he dedicated all his time to his second passion, writing and architecting software. Gabriel has since been working for over 12 years as an independent consultant, trainer, and mentor mainly on the .NET platform. He is currently working as chief software architect in a mid-size US company based in Austin TX providing software and services to the pharmaceutical industry as well as to many well-known hospitals and universities throughout the US and in many other countries around the world. Gabriel is passionate about software development and tries to make the life of developers easier by providing guidelines and frameworks to reduce friction in the software development process. Gabriel is married and father of four children and during his spare time likes hiking in the mountains, cooking and reading.
This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Matt Frear

    I’m enjoying this series of posts, but why does Los Techies only serve a preview of each post via RSS and not the whole post like they used to? Annoying cos I do a lot of my reading offline.

  • Phil Lee

    Hi Gabriel,
    I’ve been following this series and found it to be delightful! Thanks so much for going through things slowly – I’ve found the repetition really helps me to remember the important concepts.
    I’ve also completed the AngularJS tutorial series on the AngularJS website, and in part 11 they discus using a service to encapsulate some of the API calls, and provide this service as a dependency to controllers so they don’t have to call the API directly (using $http). Question – did you take a similar approach for your application, or did you just call $http from the controllers? If so, did you initially make the calls directly from controllers, and move to a service once you reached a certain point, or just use service/s from the start?
    Thanks,
    Phil

    • gabrielschenker

      I strongly believe that we should always start simple and then refactor when it makes sense, that is, when the code becomes “ugly” or “smelly”. So yes, I usually start using the $http service directly in the controller and have no additional abstraction in place. As soon as I see that the code is getting more involved I start to extract pieces into its own service. Does this make sense?

      • Phil Lee

        Thanks Gabriel, that does indeed make sense. No point making things more complex than they need to be eh?

  • Pingback: AngularJS-Learning | Nisar Khan