Ангуляр поставляется с различными видами служб или сервисов, каждый из которых применяется в своей ситуации.
Имейте в виду, что сервисы, не зависимо от типа, это всегда синглтоны (одиночки).
Примечание: Синглтон это шаблон проектирования, который ограничивает класс таким образом, что у него может быть только один экземпляр. Именно с этим экземпляром и ведется работа везде, где он используется.
Перейдем к типам сервисов
Constant
app.constant('fooConfig', {
config1: true,
config2: "Default config2"
});
Константа часто используется для конфигурации по умолчанию в директивах. Так что если создаете директиву, и хотите помимо настройки иметь возможность передать ей некоторые стандартные параметры, константа — хороший способ сделать это.
Значение константы задается при определении и не может быть изменено другим путем. Значением константы может быть примитив или объект.
Value
app.value('fooConfig', {
config1: true,
config2: "Default config2 but it can changes"
});
Переменная подобна константе, но может быть изменена. Она часто используется для настройки директив. Переменная подобна усеченной версии фабрики, только содержит значения, которые не могут быть вычислены в самом сервисе.
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;
});
Фабрика является наиболее часто используемым сервисом. Так же она самая простая для понимания.
Фабрика это сервис, который может вернуть любой тип данных. Она не содержит правил по созданию этих данных. Нужно всего лишь вернуть что-то. При работе с объектами, мне нравится работать с шаблоном открытого модуля, но вы можете использовать другой подход, если хотите.
Как упоминал выше, все типы это синглтоны, так что, если мы изменим foo.variable
в одном месте, в других местах она тоже изменится.
Service
app.service('foo', function() {
var thisIsPrivate = "Private";
this.variable = "This is public";
this.getPrivate = function() {
return thisIsPrivate;
};
});
Сервис (не путайте общее название с конкретным типом) работает так же как фабрика. Разница в том, что сервис использует конструктор, поэтому, когда используете его в первый раз, он выполнит new Foo();
для создания экземпляра объекта. Имейте в виду, что этот же объект вернется и в других местах, если использовать там этот сервис.
Фактически сервис эквивалентен следующему коду:
app.factory('foo2', function() {
return new Foobar();
});
function Foobar() {
var thisIsPrivate = "Private";
this.variable = "This is public";
this.getPrivate = function() {
return thisIsPrivate;
};
}
Foobar
является классом, и мы создаем его экземпляр на нашей фабрике, используем его первый раз, а затем возвращаем. Как и сервис, экземпляр класса Foobar
будет создан только однажды и в следующий раз фабрика вернет этот же экземпляр снова.
Если у нас уже есть класс, и мы хотим использовать его в нашем сервисе, можем сделать так:
app.service('foo3', Foobar);
Provider
Провайдер это фабрика, настроенная особым образом. Фактически фабрика из последних примеров будет выглядеть как-то так:
app.provider('foo', function() {
return {
$get: function() {
var thisIsPrivate = "Private";
function getPrivate() {
return thisIsPrivate;
}
return {
variable: "This is public",
getPrivate: getPrivate
};
}
};
});
Провайдер ожидает функцию $get
, которая будет тем, что мы внедряем в другие части нашего приложения. Поэтому, когда мы внедряем foo
в контроллер, то внедряется функция $get
Почему нужно использовать эту форму, когда фабрика гораздо проще? Потому что провайдер можно настроить в конфигурационной функции. Так что можно сделать что-то вроде этого:
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');
});
Здесь мы вынесли thisIsPrivate
за пределы функции $get
, а затем создали функцию setPrivate
, чтобы иметь возможность изменить thisIsPrivate
в функции конфигурации. Почему нужно это делать? Не проще ли просто добавить сеттер на фабрике? Тут другая цель.
Мы хотим внедрить определенный объект, но хотим иметь способ настроить его для своих нужд. Например: для сервиса-обертки ресурса, использующего JSONP, хотим иметь возможность настроить URL, который будет использоваться нами или сторонними сервисами, такими как restangular
. Провайдер позволяет нам настроить предварительно его для наших целей.
Обратите внимание, что в конфигурационной функции нужно указать в качестве имени nameProvider
, а не name
. name
указывается во всех других случаях.
Видя это, вспоминаем, что уже настраивали некоторые сервисы в наших приложениях, например, в $routeProvider
и $locationProvider
настраивают роутинг и html5mode соответственно.
Бонус 1: Декоратор
Итак, вы решили, что какому-то сервису foo
, не хватает функции greet
и вы хотите ее добавить. Стоит ли изменять фабрику? Нет! Можно ее декорировать:
app.config(function($provide) {
$provide.decorator('foo', function($delegate) {
$delegate.greet = function() {
return "Hello, I am a new function of 'foo'";
};
return $delegate;
});
});
$provide
это то, что Ангуляр использует для создания всех внутренних сервисов. Мы можем использовать его вручную, если хотим, или просто использовать функции, предоставляемые в наших модулях (необходимо использовать $provide
для декорирования). $provide
имеет функцию, decorator
, которая позволяет нам декорировать наши сервисы. Она получает имя декорируемого сервиса, а колбэк получает $delegate
, который является оригинальным экземпляром сервиса.
Здесь мы можем делать все что захотим хотим, чтобы декорировать наш сервис. В нашем случае, мы добавили функцию greet
в оригинальный сервис. Затем вернули новый модифицированный сервис.
Теперь, при использовании, он будет иметь новую функцию greet
.
Возможность декорирования сервисов удобна при использовании сервисов от сторонних разработчиков, которые можно декорировать без необходимости копирования в свой проект и дальнейших модификаций.
Примечание: Константу декорировать нельзя.
Бонус 2: создание новых экземпляров
Наши сервисы синглтоны, но мы можем создать синглтон-фабрику, которая создает новые экземпляры. Прежде чем углубиться, имейте в виду, что наличие сервисов-синглтонов хороший подход, который мы не хотим менять. Но в тех редких случаях, когда нужно сгенерировать новые экземпляры, можно сделать это так:
// Наш класс
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
};
});
Здесь создаем объект Person
, который получает некоторые JSON данные для инициализации объекта. Затем создали функцию в нашем прототипе (функции в прототипе для экземпляров Person
) и функции непосредственно в Person
(подобные функции класса).
Так что у нас есть функция класса, которая создаст новый объект Person
на основе идентификатора, который мы передадим (так будет в реальном коде) и каждый экземпляр сможет обновлять сам себя. Теперь просто нужно создать сервис, который будет его использовать.
Каждый раз, когда мы вызываем personService.getById
мы создаем новый объект Person
, так что можно использовать этот сервис в различных контроллерах и даже тогда, когда фабрика является синглтоном, она создает новые объекты.
Респект Джошу Дэвиду Миллеру за его пример.
Бонус 3: CoffeeScript
CoffeeScript можно удобно сочетать с сервисами, поскольку они обеспечивают более симпатичный способ создания классов. Давайте рассмотрим Бонусный пример 2 с использованием 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
}
Сейчас он выглядит красивее, по моему скромному мнению.
Сейчас он выглядит ужаснее по скромному мнению переводчика.
Заключение
Сервисы являются одними из самых привлекательных функций Ангуляра. Существует множество способов их создания, нужно всего лишь выбрать правильный лучше всего подходящий в нашем случае
Автор: tamtakoe