Как извесно Dependency Injection – это один из основных концептов архитектуры AngularJS. Разберем несколько примеров использования сервисов в модулях.
За основу данного поста я взял вот эту статью, которую перевел и немного поправил.
Представим что у нас в модуле foo есть примитивный сервис simpleService
function simpleService(){
this.name = "simpleService";
}
angular.module('foo', [])
.service('simpleService', simpleService);
Теперь представим, что мы хотим вручную получить экземпляр этого сервиса (что в практике скорее всего делать не прийдется) и использовать. Чтобы сделать это, мы создадим новый injector и поручим ему магию создания сервиса для нас:
var myInjector = angular.injector(['foo']);
var service = myInjector.get('simpleService');
console.log(service.name); // 'simpleService'
Итак, это именно оно… самый простой случай. Но что если у нас 2 модуля с сервисами, которые имеют одинаковые имена? Что тогда? Тут уже становится интересно, давайте рассмотрим еще пример:
angular.module('foo', [])
.service('simpleService', function(){ this.name = "foo"; });
angular.module('bar', [])
.service('simpleService', function(){ this.name = "bar"; });
var fooSvc = angular.injector(['foo']).get('simpleService');
var barSvc = angular.injector(['bar']).get('simpleService');
console.log(fooSvc.name); // 'foo'
console.log(barSvc.name); // 'bar'
Это похоже на то, что мы ожидали увидеть: каждый инжектор относится только к одному модулю и поэтому сервисы изолированы. Но ведь мы можем создать инжектор, который будет относиться более чем одному модулю. Что будет тогда?
var fooSvc = angular.injector(['foo','bar']).get('simpleService');
var barSvc = angular.injector(['bar','foo']).get('simpleService');
console.log(fooSvc.name); // 'bar'
console.log(barSvc.name); // 'foo'
Видите что произошло? Модуль, который регистрируется последним, перезаписывает другие одноименные с ним модули.
Но что если модули имеют зависимости? Что это меняет?
angular.module('foo', [])
.service('simpleService', function(){ this.name = "foo"; });
angular.module('bar', ['foo'])
.service('simpleService', function(){ this.name = "bar"; });
var fooSvc = angular.injector(['foo','bar']).get('simpleService');
var barSvc = angular.injector(['bar','foo']).get('simpleService');
console.log(fooSvc.name); // 'bar'
console.log(barSvc.name); // 'bar'
И тут тоже все решает порядок, но в этом случае нужно помнить о том, что модули-зависимости основного модуля будут проинициализированы первыми, т.е. для модуля bar сначала будет проинициализирован модуль-зависимость foo.
Представим, что мы тестируем приложение и хотим заменить $httpBackend сервис на наш кастомный вариант или заменить его на mock с заранее известным поведением. Это очень просто сделать в рамках архитектуры Ангулара. Все что необходимо – это создать модуль, который будет иметь зависимость от ng module и переопределить необходимый сервис(т.е. объявить в нашем модуле сервис с таким же именем).
Замена сервиса – это хорошо, но что особенно здорово в Ангулар – возможность получить сервис сразу после его создания. Это может быть достигнуто путем использования декоратора (decorator):
angular.module('foo', [])
.service('simpleService', function(){ this.name = "foo"; });
angular.module('bar', ['foo'])
.config(function($provide){
//$provide was injected for me automatically by Angular
$provide.decorator('simpleService', function($delegate){
//$delegate is the service instance, and is
// also injected automatically by Angular
$delegate.name += "|bar";
return $delegate;
});
});
var fooSvc = angular.injector(['foo']).get('simpleService');
var barSvc = angular.injector(['bar']).get('simpleService');
console.log(fooSvc.name); // 'foo'
console.log(barSvc.name); // 'foo|bar'
(С кодом можно поиграться тут)
Не вдаваясь в подробности можно сказать что у нас есть возможность получить simpleService из foo и преобразовать его. Более подробно можно почитать в документации о методах config и decorator.