Модули AngularJS и внедрение зависимостей

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