Understanding service types in AngularJS (constant, value, factory, service, provider)

Original author: Jesus Rodriguez
  • Transfer
  • Tutorial
Angular comes with various types of services or services, each of which is used in its own situation.
Keep in mind that services, regardless of type, are always singletones (loners).

Note: Singleton is a design pattern that restricts a class so that it can only have one instance. It is with this instance that work is carried out wherever it is used.

Let's move on to the types of services

Constant


app.constant('fooConfig', {
  config1: true,
  config2: "Default config2"
});

The constant is often used for default configuration in directives. So if you create a directive, and you want to be able to pass some standard parameters to it in addition to the setting, a constant is a good way to do this.

The value of the constant is specified during determination and cannot be changed in any other way. The constant value can be a primitive or an object. Also, the constant can be configured on the module stage config. Value can be used only at the run stage and further (note from the comments).

Value


app.value('fooConfig', {
  config1: true,
  config2: "Default config2 but it can changes"
});

A variable is like a constant, but can be changed. It is often used to set up directives. The variable is similar to the truncated version of the factory, only contains values ​​that cannot be calculated in the service itself.

Factory


app.factory('foo', function() {
  var thisIsPrivate = "Private";
  function getPrivate() {
    return thisIsPrivate;
  }
  return {
    variable: "This is public",
    getPrivate: getPrivate
  };
});
// или...
app.factory('bar', function(a) {
  return a * 2;
});

Factory is the most frequently used service. It is also the easiest to understand.

A factory is a service that can return any type of data. It does not contain rules for creating this data. You just need to return something. When working with objects, I like working with an open module template , but you can use a different approach if you want.

As mentioned above, all types are singleton, so if we change foo.variablein one place, in other places it will change too.

Service


app.service('foo', function() {
  var thisIsPrivate = "Private";
  this.variable = "This is public";
  this.getPrivate = function() {
    return thisIsPrivate;
  };
});

A service (do not confuse a generic name with a specific type) works just like a factory. The difference is that the service uses the constructor, so when you use it for the first time, it will execute new Foo();to create an instance of the object. Keep in mind that the same object will return in other places if you use this service there.

In fact, the service is equivalent to the following code:

app.factory('foo2', function() {
  return new Foobar();
});
function Foobar() {
  var thisIsPrivate = "Private";
  this.variable = "This is public";
  this.getPrivate = function() {
    return thisIsPrivate;
  };
}

Foobaris a class , and we instantiate it in our factory, use it the first time, and then return it. Like the service, an instance of the class Foobarwill be created only once and the next time the factory will return the same instance again.

If we already have a class, and we want to use it in our service, we can do this:

app.service('foo3', Foobar);

Provider


A provider is a factory configured in a special way. In fact, the factory from the latest examples will look something like this:

app.provider('foo', function() {
  return {
    $get: function() {
      var thisIsPrivate = "Private";
      function getPrivate() {
        return thisIsPrivate;
      }
      return {
        variable: "This is public",
        getPrivate: getPrivate
      };
    }
  };
});

The provider expects a feature $getthat will be what we embed in other parts of our application. Therefore, when we embed foo in the controller, the function is implemented. $get

Why do I need to use this form when the factory is much easier? Because the provider can be configured in the configuration function. So you can do something like this:

app.provider('foo', function() {
  var thisIsPrivate = "Private";
  return {
    setPrivate: function(newVal) {
      thisIsPrivate = newVal;
    },
    $get: function() {
      function getPrivate() {
        return thisIsPrivate;
      }
      return {
        variable: "This is public",
        getPrivate: getPrivate
      };
    }
  };
});
app.config(function(fooProvider) {
  fooProvider.setPrivate('New value from config');
});

Here we moved thisIsPrivateoutside the function $get, and then created the function setPrivateto be able to change thisIsPrivatethe configuration in the function. Why do you need to do this? Isn't it easier to just add a setter in a factory? There is another goal.

We want to implement a specific object, but we want to have a way to customize it for our needs. For example: for a service wrapper for a resource using JSONP, we want to be able to configure the URL that will be used by us or third-party services, such as restangular. The provider allows us to pre-configure it for our purposes.

Please note that in the configuration function you need to specify as a name nameProvider, not name. nameindicated in all other cases.

Seeing this, we recall that we already configured some services in our applications, for example, in $routeProviderand $locationProviderconfigure routing and html5mode, respectively.

Bonus 1: Decorator


So, you have decided that some service foolacks a function greetand you want to add it. Should I change the factory? Not! You can decorate it:

app.config(function($provide) {
  $provide.decorator('foo', function($delegate) {
    $delegate.greet = function() {
      return "Hello, I am a new function of 'foo'";
    };
    return $delegate;
  });
});

$providethis is what Angular uses to create all internal services. We can use it manually if we want, or just use the functions provided in our modules (must be used $providefor decoration). $providehas a function, decoratorwhich allows us to decorate our services. She gets the name of the decorated service, and gets the callback $delegate, which is the original instance of the service.

Here we can do whatever we want, to decorate our service. In our case, we added a function greetto the original service. Then returned a new modified service.

Now, when used, it will have a new function greet.

The ability to decorate services is convenient when using services from third-party developers, which can be decorated without the need for copying to your project and further modifications.

Note: You cannot decorate a constant.

Bonus 2: Create New Instances


Our services are singleton, but we can create a singleton factory that creates new instances. Before delving into it, keep in mind that having singleton services is a good approach that we don't want to change. But in those rare cases when you need to generate new instances, you can do it like this:

// Наш класс
function Person( json ) {
  angular.extend(this, json);
}
Person.prototype = {
  update: function() {
    // Обновляем (В реальном коде :P)
    this.name = "Dave";
    this.country = "Canada";
  }
};
Person.getById = function( id ) {
  // Делаем что-то, чтобы получить Person по id
  return new Person({
    name: "Jesus",
    country: "Spain"
  });
};
// Наша фабрика
app.factory('personService', function() {
  return {
    getById: Person.getById
  };
});

Here we create an object Personthat receives some JSON data to initialize the object. Then we created a function in our prototype (functions in the prototype for instances Person) and functions directly in Person(similar class functions).

So we have a class function that will create a new object Personbased on the identifier that we pass (this will be in real code) and each instance will be able to update itself. Now you just need to create a service that will use it.

Each time we call, personService.getByIdwe create a new object Person, so that you can use this service in various controllers and even when the factory is a singleton, it creates new objects.

Respect to Josh David Miller for his example.

Bonus 3: CoffeeScript


CoffeeScript can be conveniently combined with services because they provide a nicer way to create classes. Let's look at Bonus Example 2 using CoffeeScript:

app.controller 'MainCtrl', ($scope, personService) ->
  $scope.aPerson = personService.getById(1)
app.controller 'SecondCtrl', ($scope, personService) ->
  $scope.aPerson = personService.getById(2)
  $scope.updateIt = () ->
    $scope.aPerson.update()
class Person
  constructor: (json) ->
    angular.extend @, json
  update: () ->
    @name = "Dave"
    @country = "Canada"
  @getById: (id) ->
    new Person
      name: "Jesus"
      country: "Spain"
app.factory 'personService', () ->
  {
    getById: Person.getById
  }

Now he looks prettier, in my humble opinion.
Now he looks worse in the humble opinion of the translator.

Conclusion


Services are one of the most attractive features of Angular. There are many ways to create them, you just need to choose the right one best suited in our case

Also popular now: