Как извесно 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.