Улучшить эту документациюВнедрение зависимостей
Внедрение зависимостей (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. - результаты: Если контроллер создаётся в рамках маршрута, любые значения, разрешённые как часть маршрута, становятся доступными для инъекции в контроллер.
Аннотация зависимостей
AngularJS вызывает определённые функции (например, фабрики сервисов и контроллеры) через инжектор. Вам необходимо аннотировать эти функции, чтобы инжектор знал, какие сервисы следует инжектировать в функцию. Существует три способа аннотирования вашего кода информацией об имени сервиса:
- Использование встроенной аннотации массива (предпочтительный способ)
- Использование аннотации свойства
$inject - Неявное использование имён параметров функции (с оговорками)
Встроенная аннотация массива
Это предпочтительный способ аннотирования компонентов приложения. Так написаны примеры в документации.
Например:
someModule.controller('MyController', ['$scope', 'greeter', function($scope, greeter) {
// ...
}]);
Здесь мы передаём массив, элементы которого содержат список строк (имена зависимостей) и саму функцию.
При использовании этого типа аннотации следите за тем, чтобы массив аннотаций был синхронизирован с параметрами объявления функции.
$inject Свойство аннотации
Чтобы разрешить минификаторам переименовывать параметры функций и при этом вводить правильные сервисы, функция должна быть аннотирована свойством $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, позволяют использовать неявные аннотации зависимостей в вашем приложении и автоматически добавлять аннотации inline-массивов перед минификацией. Если вы решите выбрать этот подход, вы, вероятно, захотите использовать 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 в вашей любимой книге по шаблонам проектирования.
Существует только три способа, которыми компонент (объект или функция) может получить свои зависимости:
- Компонент может создать зависимость, обычно используя оператор
new. - Компонент может найти зависимость, обратившись к глобальной переменной.
- Компонент может получить зависимость, где она нужна.
Первые два варианта создания или поиска зависимостей не оптимальны, поскольку они жестко кодируют зависимость в компоненте. Это затрудняет, если не делает невозможным, изменение зависимостей. Это особенно проблематично в тестах, где часто желательно предоставить имитационные зависимости для изоляции тестов.
Третий вариант является наиболее жизнеспособным, так как он снимает с компонента ответственность за поиск зависимости. Зависимость просто передаётся компоненту.
function SomeClass(greeter) {
this.greeter = greeter;
}
SomeClass.prototype.doSomething = function(name) {
this.greeter.greet(name);
}
В приведенном выше примере SomeClass не отвечает за создание или поиск зависимости greeter, ему просто передаётся greeter при его создании.
Это желательно, но возлагает ответственность за получение зависимости на код, создающий SomeClass.
Для управления ответственностью за создание зависимостей каждое приложение AngularJS имеет инжектор. Инжектор — это локатор сервисов, который отвечает за создание и поиск зависимостей.
Вот пример использования сервиса инжектора:
Сначала создайте модуль AngularJS, который будет содержать определение сервиса. (Пустой массив, переданный в качестве второго параметра, означает, что этот модуль не зависит от других модулей.)
// Create a module to hold the service definition
var myModule = angular.module('myModule', []);
Научите инжектор, как построить сервис greeter, который представляет собой просто объект, содержащий метод greet. Обратите внимание, что greeter зависит от сервиса $window, который будет предоставлен (инжектирован в greeter) инжектором.
// Define the `greeter` service
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');
};
}
При компиляции AngularJS HTML, он обрабатывает директиву ng-controller, которая в свою очередь запрашивает у инжектора создание экземпляра контроллера и его зависимостей.
injector.instantiate(MyController);
Всё это происходит за кулисами. Обратите внимание, что, попросив инжектор создать экземпляр класса, ng-controller может удовлетворить все зависимости MyController без того, чтобы контроллер когда-либо знал об инжекторе.
Это наилучший результат. Код приложения просто объявляет необходимые зависимости, не вступая в взаимодействие с инжектором. Такая настройка не нарушает Закон Деметера.
© 2010–2020 Google, Inc.
Licensed under the Creative Commons Attribution License 3.0.
https://code.angularjs.org/1.8.2/docs/guide/di