May 4, 2014

service in angularjs

AngularJs is different from other JavaScript client-side framework in that it use dependency injection to manage its components. Although dependency injection is not a new method, in fact it has been very successful for many years, it is a new approach in JavaScript client-side framework. So if you never use dependency injection in other language before, it is pretty confusing. The following is my notes on this topic.

There are basically two step to develop your angularjs application.

  1. package and register your components using module like the following.

    (function () {
      var module = angular.module("mymodule", ["ng"]);
      module.value("user", { firstName: "Johe", lastName: "Doe" };
      module.directive("userWidget",  function (user) {
          //user is injected here.
          return {};
      });
    })();
    
  2. Let angularjs run the show by using an injector inject the components you created.

    <body ng-app="mymodule">
      <user-widget></user-widget>
    </body>
    

In the first step, when the page is loaded, angualar bootstrap the application. In the process of bootstraping application, one important object called "injector" is created which is shared across the application. Normally your don't need to access the injector programatically, or manually create an injector by yourself, so you may have never heard about it, or you might think that module and injector are the same thing. But they are not. In fact, you can create multiple injectors, each of them use different modules. But in the following exercise, I will manually create one, because I want to know internals of module and injector.

For now, there are 5 kinds of components you write in angularjs, service, animation, filter, directive, controller. Anything which is not animation, filter, directive, or controller, is service. If the name service confuse you, you can call service as injectable. It can be anything like a string, a number, an object, a dom element or whatever. An "injectable" is not injected by module, but injected by a injector, which has information from modules. The following code create an injector.

var modules = ["ng"];
var injector = angular.injector(modules);

The services we created using module is not really service instance, but service provider. During the creation of injector, angularjs import the service provider we created. However the instance of service is not automatically created. In the following, I have modified angularjs's source code to expose the cached service providers in injector as injector._providerCache and expose the cached service instance as injector._instanceCache, in the following screen shot we can confirm this.

When the injector is asked for an component instance, the injector will firstly check whether the component instance is in cache, if it is true, then return it , if not, ask the corresponding component provider to create an instance, cache the instance, and return it like the following.

AngularJs allows your to create your service provider in several ways. module.provider() is the most flexible way among them. The provider passed into the method need be an object with "$get" method which return to the service.

module.provider()

var module = angular.module("module", []);
module.provider("greeter", function () {
  return {
    greeting: "hello",
    $get: function () {
      //here this refer to the service provider
      var greeting = this.greeting;
      return function (name) {
        console.log(greeting + "," + name);
      };
    }
  };
});
var injector = angular.injector(["module"]);
var greeter = injector.get("greeter");
greeter("Fred"); //output hello,Fred

The flexibility of module.provider() is that you customize customize it later. Let's say, the "greeter" provider is created by a third party company. And you want to customize it's behavior before it get injected. You can write the following code.

var mymodule = angular.module("mymodule", ["module"]);

mymodule.config(function (greeterProvider) {
  greeterProvider.greeting = "hi";
});

var injector = angular.injector(["mymodule"]);
var greeter = injector.get("greeter");
greeter("Fred"); //output hi,Fred

module.factory()

If you don't need such flexibility of service provider, you can use module.factory() method, which is a simplified version of module.provider() method.

var module = angular.module("module", []);
module.factory("greeter", function () {
  return function (name) {
    console.log("hello," + name);
  }
});

var injector = angular.injector(["module"]);
var greeter = injector.get("greeter");
greeter("Fred");

Behind the scene, the module.factory() still create service provider internally exactly like created by the module.provider(), the result of the following is identical to the result of above code.

module.provider("greeter", function () {
  return {
    $get: function () {
      return function (name) {
        console.log("hello," + name);
      };
    }
  };
});

module.service() -- don't use

AngularJs also has a module.service() method which is very similar to module.factory(). In fact, in the above example, you can replace the module.factory() method with module.service(), the final result is the same (but the calling method is different), like the following.

module.service("greeter", function () {
  return function (name) {
    console.log("hello," + name);
  }
});

So why do we need a service method? How is the difference between this two. The source code angularjs show that the service method internally call factory method like the following.

function service(name, constructor) {
  return factory(name, ['$injector', function ($injector) {
    return $injector.instantiate(constructor);
  }]);
} 

This means that the factory method can do all the thing that that service method can do. The difference is that provider function is treated as constructor, it is called like "new constructor()". The following shows the difference

module.service("user", function () {
  this.firstName = "John";
  this.lastName = "Doe";
});

module.factory("user", function () {
  return {
     firstName: "John",
     lastName: "Doe"
  };
});

So when should we use service method, when should we use factory method. So far I never encounter a case when I have to use service method, and factory method is not able to do so. So my opinion is that, never use "service" method, always use "factory" method, because "factory" method simpler and more capable.

module.value()

If you look at the factory method, the "$get" member of real provider is a function which return a instance of the provider. Why can't the "$get" member be just the instance itself? Ok, angulajs has another method "module.value()" which try to meed this need. The value method simply return the instance like the following.

module.value("greeter", funnction (name) {
  console.log("hello," + name);
});

module.value("user", {
 firstName: "John",
 lastName: "Doe"
});

However, internally, the value method is like the following

function value( name, val ) {
 return factory( name, function() {
   return val;
 });
}

So the value method still convert the value into a provider which provide the value internally. But the question, "Why can't the "$get" member be just the instance itself instead of a function which provide the value?" is still not answered. What is the benefit of doing that. Let's look at the following code.

module.value("user", {
 firstName: "John",
 lastName: "Doe"
});

module.factory("greeter", function (user) {
  return function () {
   console.log("hello," + user.firstName + "," + user.lastName;
  };
});

In the above code, the greeter depends on user, the user is passed in the factory method by the injector. If the "$get" is the instance, but not a function, the "user" dependency will not be able to injected. However, if your instance does not have external dependencies which need to be injected by injector, you should use "value" method, which is more expressive, otherwise use "factory" method instead.

module.constant()

All the method above create service provider, which can return a service instance. module.constant() is different in that it does not create service provider, but it create the service instance directly. What this means is that it cannot be configurable using module.config(fn). Before you use injector.get(constantKey) to get the instance, it is already created and cached, like the following.

module.config(fn)

AngularJs has one more method module.config(fn) allow you config imported service provider. When an injector is being created, all the service providers created by the module.provider(), module.factory(), module.service(), module.value() is imported first using $provide. Then the config functions will run. The $provide dependencies can also be injected into the functions, the service providers created can also be injected into the functions. For example, if you create a service call "greeter", the "greeterProvider" can be injected into the config functions, but not the "greeter", because at this stage you are about to customize "greeterProvider" to create "greeter", that is why "greeter" can not be injected. This has been shown in previous demo

Also module.config allow you to create service provider using $provide object. The $provide object has the following method

$provide.provider(key, value); 
$provide.factory(key, value);
$provide.service(key, value);
$provide.value(key, value);
$provide.constant(key, value);

$provide.decorator(key, value);

AngularJs use the first 5 methods to import the service provider created using module method. For example, $provide.provider(key, value) is used to import the provider created by module.provider(key, value), so far and so on. But the question is why we want to use $provide in config function to create service provider directly instead of using the method of module, as they looks like do the same thing? In fact, in AngularJs use the config method the directly create service provider, like the following, but not using module methods.

var ng = angular.module("ng", []);
ng.config(function ($provide) {
$provide.provider({                              
    $anchorScroll: $AnchorScrollProvider,        
    $animate: $AnimateProvider,                  
    $browser: $BrowserProvider,                  
    $cacheFactory: $CacheFactoryProvider,        
    $controller: $ControllerProvider,            
    //..and more 
});                            
});                  

Honestly, I don't have an definite answer. But I think it is different way to organize your code, maybe AngularJs team think that is better for code minification or file organization. But the module method is good enough to organize application level development (vs framework development).

The $provide.decorator() is most interesting method of $provide object. You can use it to extend existing service provider. The following is an example

var module1 = angular.module("module1", []);
module1.value("name", "John");


var module2 = angular.module("module2", ["module1"]);
module2.config(function ($provide) {
  $provide.decorator("name", ["$delegate", function (oldInstance) {
    return oldInstance + "," + "Doe";
  }]);
});


var injector = angular.injector(["module2"]);
injector.get("name"); //name is John,Doe

If you have question about service dependency injection in angular, please leave me a note below.