
Understanding service types in AngularJS (constant, value, factory, service, provider)
- 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
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).
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 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
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
In fact, the service is equivalent to the following code:
If we already have a class, and we want to use it in our service, we can do this:
A provider is a factory configured in a special way. In fact, the factory from the latest examples will look something like this:
The provider expects a feature
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:
Here we moved
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
Please note that in the configuration function you need to specify as a name
Seeing this, we recall that we already configured some services in our applications, for example, in
So, you have decided that some service
Here we can do whatever we want, to decorate our service. In our case, we added a function
Now, when used, it will have a new function
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.
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:
Here we create an object
So we have a class function that will create a new object
Each time we call,
Respect to Josh David Miller for his example.
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:
Now he looks prettier, in my humble opinion.
Now he looks worse in the humble opinion of the translator.
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
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.variable
in 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;
};
}
Foobar
is 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 Foobar
will 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
$get
that 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
thisIsPrivate
outside the function $get
, and then created the function setPrivate
to be able to change thisIsPrivate
the 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
. name
indicated in all other cases.Seeing this, we recall that we already configured some services in our applications, for example, in
$routeProvider
and $locationProvider
configure routing and html5mode, respectively.Bonus 1: Decorator
So, you have decided that some service
foo
lacks a function greet
and 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;
});
});
$provide
this 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 $provide
for decoration). $provide
has a function, decorator
which 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
greet
to 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
Person
that 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
Person
based 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.getById
we 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