Spec-Zone.ru › Angular.js 1.6

Улучшить эту документациюВнедрение зависимостей

Внедрение зависимостей (DI) — это шаблон проектирования программного обеспечения, который описывает, как компоненты получают свои зависимости.

Подсистема инжектора AngularJS отвечает за создание компонентов, разрешение их зависимостей и предоставление их другим компонентам по запросу.

Использование внедрения зависимостей

Внедрение зависимостей широко используется в AngularJS. Вы можете использовать его при определении компонентов или при предоставлении run и config блоков для модуля.

  • Сервисы, директивы, фильтры и анимации определяются инжектируемым фабричным методом или конструкторной функцией и могут быть инжектированы "сервисами", "значениями" и "константами" в качестве зависимостей.

  • Контроллеры определяются конструкторной функцией, которая может быть инжектирована любыми "сервисами" и "значениями" в качестве зависимостей, но также может получать "специальные зависимости". Смотрите Контроллеры ниже для списка этих специальных зависимостей.

  • Метод run принимает функцию, которая может быть инжектирована "сервисами", "значениями" и "константами" в качестве зависимостей. Обратите внимание, что вы не можете инжектировать "провайдеры" в run блоки.

  • Метод config принимает функцию, которая может быть инжектирована "провайдерами" и "константами" в качестве зависимостей. Обратите внимание, что вы не можете инжектировать "сервисы" или "значения" в конфигурацию.

  • Метод provider может быть инжектирован только другими "провайдерами". Однако инжектировать можно только те, что были зарегистрированы предварительно. Это отличается от сервисов, где порядок регистрации не важен.

См. Модули для получения дополнительной информации о run и config блоках и Провайдеры для получения дополнительной информации о различных типах провайдеров.

Фабричные методы

Способ определения директивы, сервиса или фильтра — это фабричная функция. Фабричные методы регистрируются в модулях. Рекомендуемый способ объявления фабрик:

angular.module('myModule', [])
.factory('serviceId', ['depService', function(depService) {
  // ...
}])
.directive('directiveName', ['depService', function(depService) {
  // ...
}])
.filter('filterName', ['depService', function(depService) {
  // ...
}]);

Методы модуля

Мы можем указать функции для выполнения в момент конфигурации и выполнения для модуля, вызвав методы config и run. Эти функции инжектируются зависимостями, как и фабричные функции выше.

angular.module('myModule', [])
.config(['depProvider', function(depProvider) {
  // ...
}])
.run(['depService', function(depService) {
  // ...
}]);

Контроллеры

Контроллеры — это "классы" или "конструкторные функции", которые отвечают за обеспечение поведения приложения, поддерживающего декларативную разметку в шаблоне. Рекомендуемый способ объявления контроллеров — использование нотации массивов:

someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) {
  ...
  $scope.aMethod = function() {
    ...
  }
  ...
}]);

В отличие от сервисов, в приложении может быть много экземпляров одного типа контроллера.

Кроме того, контроллерам доступны дополнительные зависимости:

  • $scope: Контроллеры связаны с элементом в DOM и поэтому получают доступ к области видимости. Другие компоненты (например, сервисы) имеют доступ только к сервису $rootScope.
  • resolves: Если контроллер создается в рамках маршрута, то все значения, разрешенные в рамках маршрута, становятся доступными для инъекции в контроллер.

Аннотация зависимостей

AngularJS вызывает определенные функции (например, фабрики сервисов и контроллеры) через инжектор. Вам необходимо аннотировать эти функции, чтобы инжектор знал, какие сервисы нужно инжектировать в функцию. Существует три способа аннотирования вашего кода с информацией о имени сервиса:

  • Использование аннотации массива встраивания (предпочтительно)
  • Использование аннотации свойства $inject
  • Неявное определение по именам параметров функции (имеет недостатки)

Аннотация массива встраивания

Это предпочтительный способ аннотации компонентов приложения. Именно так написаны примеры в документации.

Например:

someModule.controller('MyController', ['$scope', 'greeter', function($scope, greeter) {
  // ...
}]);

Здесь мы передаем массив, элементы которого состоят из списка строк (названий зависимостей), за которыми следует сама функция.

При использовании этого типа аннотации следует следить за синхронизацией массива аннотаций с параметрами в объявлении функции.

$inject Property Annotation

Чтобы позволить минификаторам переименовать параметры функций и при этом иметь возможность инжектировать правильные сервисы, функция должна быть аннотирована свойством $inject. Свойство $inject представляет собой массив имён сервисов для инъекции.

var MyController = function($scope, greeter) {
  // ...
}
MyController.$inject = ['$scope', 'greeter'];
someModule.controller('MyController', MyController);

В этом случае порядок значений в массиве $inject должен соответствовать порядку параметров в MyController.

Так же, как и с аннотацией массива, вам необходимо следить за синхронизацией $inject с параметрами в объявлении функции.

Неявная аннотация

Внимание: Если вы планируете минифицировать свой код, имена ваших сервисов будут переименованы и приведут к ошибке в приложении.

Самый простой способ получить зависимости — предположить, что имена параметров функции являются именами зависимостей.

someModule.controller('MyController', function($scope, greeter) {
  // ...
});

Для данной функции инжектор может определить имена сервисов для инъекции, проанализировав объявление функции и извлекая имена параметров. В приведенном выше примере $scope и greeter — это два сервиса, которые необходимо инжектировать в функцию.

Одним из преимуществ этого подхода является отсутствие массива имен для синхронизации с параметрами функции. Вы также можете свободно изменять порядок зависимостей.

Однако этот метод не будет работать с JavaScript минификаторами/обфускаторами из-за способа переименования ими параметров.

Инструменты, такие как ng-annotate, позволяют использовать неявные аннотации зависимостей в вашем приложении и автоматически добавлять аннотации массива встраивания перед минификацией. Если вы решите использовать этот подход, вам, вероятно, захочется использовать ng-strict-di.

Из-за этих ограничений мы рекомендуем избегать этого стиля аннотации.

Использование строгого внедрения зависимостей

Вы можете добавить директиву ng-strict-di на тот же элемент, что и ng-app для включения строгого режима DI:

<!doctype html>
<html ng-app="myApp" ng-strict-di>
<body>
  I can add: {{ 1 + 2 }}.
  <script src="angular.js"></script>
</body>
</html>

Строгий режим генерирует ошибку всякий раз, когда сервис пытается использовать неявные аннотации.

Рассмотрим этот модуль, который включает сервис willBreak, использующий неявное DI:

angular.module('myApp', [])
.factory('willBreak', function($rootScope) {
  // $rootScope is implicitly injected
})
.run(['willBreak', function(willBreak) {
  // AngularJS will throw when this runs
}]);

При создании сервиса willBreak AngularJS сгенерирует ошибку из-за строгого режима. Это полезно при использовании инструмента, такого как ng-annotate, чтобы гарантировать, что все компоненты вашего приложения имеют аннотации.

Если вы используете ручную загрузку, вы также можете использовать строгий DI, предоставив strictDi: true в необязательном аргументе конфигурации:

angular.bootstrap(document, ['myApp'], {
  strictDi: true
});

Зачем использовать внедрение зависимостей?

Этот раздел мотивирует и объясняет использование DI в AngularJS. Как использовать DI, см. выше.

Для углубленного обсуждения DI см. Внедрение зависимостей на Википедии, Инверсия управления Мартина Фаулера или прочитайте о DI в любимой книге по шаблонам проектирования.

Существует только три способа, которыми компонент (объект или функция) может получить свои зависимости:

  1. Компонент может создать зависимость, обычно используя оператор new.
  2. Компонент может найти зависимость, обратившись к глобальной переменной.
  3. Компонент может получить зависимость, где она необходима.

Два первых варианта создания или поиска зависимостей не оптимальны, поскольку они жестко связывают зависимость с компонентом. Это затрудняет, если не делает невозможным, модификацию зависимостей. Это особенно проблематично при тестировании, когда часто желательно предоставить имитационные зависимости для изоляции тестов.

Третий вариант является наиболее жизнеспособным, поскольку снимает с компонента ответственность за поиск зависимости. Зависимость просто передается компоненту.

function SomeClass(greeter) {
  this.greeter = greeter;
}

SomeClass.prototype.doSomething = function(name) {
  this.greeter.greet(name);
}

В приведенном выше примере SomeClass не заботится о создании или поиске зависимости greeter, ему просто передают greeter при создании.

Это желательно, но возлагает ответственность за получение зависимости на код, создающий SomeClass.

Для управления ответственностью за создание зависимостей в каждом приложении AngularJS есть инжектор injector. Инжектор — это локатор сервисов, который отвечает за создание и поиск зависимостей.

Вот пример использования сервиса инжектора:

// Provide the wiring information in a module
var myModule = angular.module('myModule', []);

Обучите инжектор, как создавать сервис greeter. Обратите внимание, что greeter зависит от сервиса $window. Сервис greeter — это объект, содержащий метод greet.

myModule.factory('greeter', function($window) {
  return {
    greet: function(text) {
      $window.alert(text);
    }
  };
});

Создайте новый инжектор, который может предоставлять компоненты, определенные в нашем модуле myModule и запросите наш сервис greeter у инжектора. (Это обычно выполняется автоматически загрузкой AngularJS).

var injector = angular.injector(['ng', 'myModule']);
var greeter = injector.get('greeter');

Запрос зависимостей решает проблему жесткой связи, но это также означает, что инжектор должен передаваться по всему приложению. Передача инжектора нарушает закон Деметер. Чтобы исправить это, мы используем декларативную нотацию в наших HTML-шаблонах, чтобы передать инжектору ответственность за создание компонентов, как в этом примере:

<div ng-controller="MyController">
  <button ng-click="sayHello()">Hello</button>
</div>
function MyController($scope, greeter) {
  $scope.sayHello = function() {
    greeter.greet('Hello World');
  };
}

При компиляции HTML AngularJS обрабатывает директиву ng-controller, которая в свою очередь запрашивает у инжектора создание экземпляра контроллера и его зависимостей.

injector.instantiate(MyController);

Всё это происходит за кулисами. Обратите внимание, что, попросив ng-controller инициализировать класс, инжектор может удовлетворить все зависимости MyController без того, чтобы контроллер когда-либо узнал об инжекторе.

Это лучший результат. Код приложения просто объявляет необходимые зависимости, не взаимодействуя с инжектором. Эта настройка не нарушает Закон Деметера.

Примечание: AngularJS использует инъекцию конструктора.

© 2010–2018 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://code.angularjs.org/1.6.9/docs/guide/di

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API