Улучшить эту документациюПоставщики
Каждое создаваемое вами веб-приложение состоит из объектов, которые взаимодействуют, чтобы выполнить задачи. Для работы приложения эти объекты необходимо инициализировать и связать друг с другом. В приложениях AngularJS большинство этих объектов инициализируются и связываются автоматически службой инжектора.
Инжектор создаёт два типа объектов: службы и специализированные объекты.
Сервисы — это объекты, API которых определяет разработчик, создающий сервис.
Специализированные объекты соответствуют определённому API фреймворка AngularJS. Эти объекты являются контроллерами, директивами, фильтрами или анимациями.
Инжектору необходимо знать, как создавать эти объекты. Вы сообщаете ему об этом, зарегистрировав «рецепт» создания вашего объекта в инжекторе. Существует пять типов рецептов.
Наиболее подробный, но и наиболее комплексный, — это рецепт поставщика. Остальные четыре типа рецептов — Значение, Фабрика, Сервис и Константа — являются просто синтаксическим сахаром поверх рецепта поставщика.
Давайте рассмотрим различные сценарии создания и использования служб с помощью различных типов рецептов. Мы начнём с самого простого случая, когда в разных частях вашего кода требуется общая строка, и достигнем этого с помощью рецепта Значение.
Примечание: несколько слов о модулях
Для того, чтобы инжектор знал, как создавать и связывать все эти объекты, ему нужен реестр «рецептов». Каждый рецепт имеет идентификатор объекта и описание того, как создать этот объект.
Каждый рецепт принадлежит модулю AngularJS. Модуль AngularJS — это контейнер, содержащий один или несколько рецептов. И поскольку вручную отслеживать зависимости модулей — не самая приятная задача, модуль может содержать информацию о зависимостях от других модулей.
Когда приложение AngularJS запускается с заданным модулем приложения, AngularJS создаёт новый экземпляр инжектора, который, в свою очередь, создаёт реестр рецептов как объединение всех рецептов, определённых в базовом модуле «ng», модуле приложения и его зависимостях. Затем инжектор обращается к реестру рецептов, когда ему нужно создать объект для вашего приложения.
Рецепт Значение
Предположим, что мы хотим иметь очень простую службу под названием «clientId», которая предоставляет строку, представляющую идентификатор аутентификации, используемый для некоторого удалённого API. Вы определите её так:
var myApp = angular.module('myApp', []);
myApp.value('clientId', 'a12345654321x');
Обратите внимание, как мы создали модуль AngularJS под названием myApp, и указали, что это определение модуля содержит «рецепт» для построения службы clientId, которая в данном случае является простой строкой.
Вот как вы можете отобразить её с помощью привязки данных AngularJS:
myApp.controller('DemoController', ['clientId', function DemoController(clientId) {
this.clientId = clientId;
}]);
<html ng-app="myApp">
<body ng-controller="DemoController as demo">
Client ID: {{demo.clientId}}
</body>
</html>
В этом примере мы использовали рецепт Значение для определения значения, которое будет предоставлено, когда DemoController запросит службу с идентификатором «clientId».
Перейдём к более сложным примерам!
Рецепт Фабрика
Рецепт Значение очень прост в написании, но ему не хватает важных функций, которые нам часто нужны при создании служб. Теперь давайте посмотрим на более мощного брата рецепта Значение — рецепт Фабрика. Рецепт Фабрика добавляет следующие возможности:
- возможность использовать другие службы (иметь зависимости)
- инициализация службы
- отложенная/ленивая инициализация
Рецепт Фабрика создаёт новую службу с помощью функции с нулём или более аргументами (эти аргументы — зависимости от других служб). Возвращаемое значение этой функции — экземпляр службы, созданный этим рецептом.
Примечание: все службы в AngularJS являются синглтонами. Это означает, что инжектор использует каждый рецепт не более одного раза для создания объекта. Затем инжектор кеширует ссылку для всех будущих потребностей.
Поскольку Фабрика — это более мощная версия рецепта Значение, с её помощью можно создать ту же самую службу. Используя наш предыдущий пример рецепта Значение clientId, мы можем переписать его как рецепт Фабрики так:
myApp.factory('clientId', function clientIdFactory() {
return 'a12345654321x';
});
Но учитывая, что токен — просто строковая константа, использование рецепта Значение всё же более уместно, так как код становится более понятным.
Однако предположим, что мы также хотели бы создать службу, которая вычисляет токен, используемый для аутентификации на удалённом API. Этот токен будет называться apiToken и будет вычисляться на основе значения clientId и секрета, хранящегося в локальном хранилище браузера:
myApp.factory('apiToken', ['clientId', function apiTokenFactory(clientId) {
var encrypt = function(data1, data2) {
// NSA-proof encryption algorithm:
return (data1 + ':' + data2).toUpperCase();
};
var secret = window.localStorage.getItem('myApp.secret');
var apiToken = encrypt(clientId, secret);
return apiToken;
}]);
В данном коде мы видим, как служба apiToken определена с помощью рецепта Фабрики, зависящего от службы clientId. Затем служба-фабрика использует шифрование, устойчивое к NSA, для создания токена аутентификации.
<serviceId>Factory (например, apiTokenFactory). Хотя эта система именования не является обязательной, она помогает при навигации по кодовой базе или при просмотре стеков вызовов в отладчике. Так же, как и с рецептом Значение, рецепт Фабрика может создавать службы любого типа, будь то примитив, литерал объекта, функция или даже экземпляр пользовательского типа.
Рецепт Сервис
Разработчики JavaScript часто используют пользовательские типы для написания объектно-ориентированного кода. Давайте рассмотрим, как мы можем запустить единорога в космос с помощью нашей службы unicornLauncher, которая является экземпляром пользовательского типа:
function UnicornLauncher(apiToken) {
this.launchedCount = 0;
this.launch = function() {
// Make a request to the remote API and include the apiToken
...
this.launchedCount++;
}
}
Теперь мы готовы запускать единорогов, но обратите внимание, что UnicornLauncher зависит от нашей apiToken. Мы можем удовлетворить эту зависимость от apiToken с помощью рецепта Фабрики:
myApp.factory('unicornLauncher', ["apiToken", function(apiToken) {
return new UnicornLauncher(apiToken);
}]);
Однако это именно тот случай, для которого рецепт Сервис наиболее подходит.
Рецепт Сервис создаёт службу так же, как и рецепты Значение или Фабрика, но делает это, вызывая конструктор с оператором new. Конструктор может принимать ноль или более аргументов, которые представляют зависимости, необходимые для экземпляра этого типа.
Примечание: Рецепты Сервиса следуют паттерну проектирования под названием инъекция конструктора.
Поскольку у нас уже есть конструктор для нашего типа UnicornLauncher, мы можем заменить рецепт Фабрики выше рецептом Сервис так:
myApp.service('unicornLauncher', ["apiToken", UnicornLauncher]);
Гораздо проще!
Рецепт Поставщик
Как уже упоминалось во введении, рецепт Поставщик — это основной тип рецепта, а все остальные типы рецептов — просто синтаксический сахар поверх него. Это самый подробный рецепт с наибольшим количеством возможностей, но для большинства служб это избыточно.
Рецепт Поставщик синтаксически определяется как пользовательский тип, который реализует метод $get. Этот метод — функция-фабрика, как и та, которую мы используем в рецепте Фабрики. Фактически, если вы определяете рецепт Фабрики, автоматически создаётся пустой тип поставщика с методом $get , установленным на вашу функцию-фабрику.
Вы должны использовать рецепт Поставщика только тогда, когда хотите предоставить API для конфигурации приложения на уровне всего приложения, которая должна быть выполнена до запуска приложения. Это обычно интересно только для многократно используемых служб, поведение которых может незначительно отличаться между приложениями.
Предположим, что наша служба unicornLauncher настолько замечательна, что её используют многие приложения. По умолчанию запускатель запускает единорогов в космос без какой-либо защитной оболочки. Но на некоторых планетах атмосфера настолько плотная, что мы должны обернуть каждого единорога в фольгу перед отправкой в межгалактическое путешествие, иначе они сгорят, проходя через атмосферу. Тогда было бы здорово, если бы мы могли настроить запускатель для использования защитной фольги для каждого запуска в приложениях, которым это нужно. Мы можем сделать его настраиваемым следующим образом:
myApp.provider('unicornLauncher', function UnicornLauncherProvider() {
var useTinfoilShielding = false;
this.useTinfoilShielding = function(value) {
useTinfoilShielding = !!value;
};
this.$get = ["apiToken", function unicornLauncherFactory(apiToken) {
// let's assume that the UnicornLauncher constructor was also changed to
// accept and use the useTinfoilShielding argument
return new UnicornLauncher(apiToken, useTinfoilShielding);
}];
});
Чтобы включить защиту фольгой в нашем приложении, нам нужно создать функцию конфигурации с помощью API модуля и передать в неё UnicornLauncherProvider:
myApp.config(["unicornLauncherProvider", function(unicornLauncherProvider) {
unicornLauncherProvider.useTinfoilShielding(true);
}]);
Обратите внимание, что поставщик единорогов передаётся в функцию конфигурации. Эта инъекция выполняется инжектором поставщиков, который отличается от обычного инжектора экземпляра тем, что он инициализирует и связывает (инжектирует) все экземпляры поставщиков только один раз.
Во время начальной загрузки приложения, перед тем как AngularJS начнёт создание всех служб, он настраивает и инициализирует все поставщики. Мы называем эту фазу жизненного цикла приложения фазой конфигурации. Во время этой фазы к услугам не доступны, поскольку они ещё не были созданы.
После завершения фазы конфигурации взаимодействие с поставщиками запрещено, и начинается процесс создания служб. Мы называем эту часть жизненного цикла приложения фазой запуска.
Рецепт Константа
Мы только что узнали, как AngularJS разделяет жизненный цикл на фазу конфигурации и фазу запуска и как вы можете предоставить конфигурацию вашему приложению с помощью функции конфигурации. Поскольку функция конфигурации выполняется в фазе конфигурации, когда доступны не все службы, она не имеет доступа даже к простым объектам значений, созданным с помощью рецепта Значение.
Поскольку простые значения, такие как префиксы URL, не имеют зависимостей или конфигурации, часто бывает полезно сделать их доступными как в фазе конфигурации, так и в фазе запуска. Для этого предназначен рецепт Константа.
Предположим, что наша служба unicornLauncher может поставить на единорога имя планеты, с которой он запускается, если это имя было предоставлено во время фазы конфигурации. Имя планеты — специфично для приложения и используется также различными контроллерами во время работы приложения. Тогда мы можем определить имя планеты как константу:
myApp.constant('planetName', 'Greasy Giant');
Затем мы могли бы настроить UnicornLauncherProvider следующим образом:
myApp.config(['unicornLauncherProvider', 'planetName', function(unicornLauncherProvider, planetName) {
unicornLauncherProvider.useTinfoilShielding(true);
unicornLauncherProvider.stampText(planetName);
}]);
И поскольку рецепт Константа делает значение доступным и во время выполнения, как и рецепт Значение, мы можем использовать его и в нашем контроллере и шаблоне:
myApp.controller('DemoController', ["clientId", "planetName", function DemoController(clientId, planetName) {
this.clientId = clientId;
this.planetName = planetName;
}]);
<html ng-app="myApp">
<body ng-controller="DemoController as demo">
Client ID: {{demo.clientId}}
<br>
Planet Name: {{demo.planetName}}
</body>
</html>
Объекты специального назначения
Ранее мы упоминали, что у нас также есть объекты специального назначения, которые отличаются от сервисов. Эти объекты расширяют фреймворк как плагины и, следовательно, должны реализовывать интерфейсы, заданные AngularJS. Эти интерфейсы — Контроллер, Директива, Фильтр и Анимация.
Инструкции инжектору по созданию этих специальных объектов (за исключением объектов контроллера) используют рецепт Фабрики за кулисами.
Давайте посмотрим, как мы создадим очень простой компонент с помощью API директивы, зависящий от только что определённой нами константы planetName и отображающий имя планеты, в нашем случае: «Имя планеты: Жирный гигант».
Поскольку директивы регистрируются через рецепт Фабрики, мы можем использовать тот же синтаксис, что и с фабриками.
myApp.directive('myPlanet', ['planetName', function myPlanetDirectiveFactory(planetName) {
// directive definition object
return {
restrict: 'E',
scope: {},
link: function($scope, $element) { $element.text('Planet: ' + planetName); }
}
}]);
Затем мы можем использовать компонент так:
<html ng-app="myApp"> <body> <my-planet></my-planet> </body> </html>
Используя рецепты Фабрики, вы также можете определять фильтры и анимации AngularJS, но контроллеры немного особенные. Вы создаёте контроллер как пользовательский тип, который объявляет свои зависимости как аргументы для своей функции-конструктора. Затем этот конструктор регистрируется в модуле. Давайте посмотрим на DemoController, созданный в одном из ранних примеров:
myApp.controller('DemoController', ['clientId', function DemoController(clientId) {
this.clientId = clientId;
}]);
Контроллер DemoController создаётся с помощью своего конструктора каждый раз, когда приложению требуется экземпляр DemoController (в нашем простом приложении это происходит только один раз). В отличие от сервисов, контроллеры не являются синглтонами. Конструктор вызывается со всеми необходимыми сервисами, в нашем случае сервисом clientId.
Заключение
В заключение давайте подведем итоги наиболее важным моментам:
- Инжектор использует рецепты для создания двух типов объектов: сервисов и объектов специального назначения.
- Существует пять типов рецептов, определяющих способ создания объектов: Value, Factory, Service, Provider и Constant.
- Factory и Service являются наиболее часто используемыми рецептами. Единственное различие между ними заключается в том, что рецепт Service лучше подходит для объектов пользовательского типа, в то время как Factory может создавать JavaScript-примитивы и функции.
- Рецепт Provider является основным типом рецепта, а все остальные являются лишь синтаксическим сахаром над ним.
- Provider является наиболее сложным типом рецепта. Вам он не нужен, если вы не создаёте многократно используемый фрагмент кода, которому требуется глобальная конфигурация.
- Все объекты специального назначения, кроме контроллера, определяются с помощью рецептов Factory.
| Функциональные возможности / Тип рецепта | Factory | Service | Value | Constant | Provider |
|---|---|---|---|---|---|
| может иметь зависимости | да | да | нет | нет | да |
| использует дружественный к типу инжект | нет | да | да* | да* | нет |
| объект доступен на фазе конфигурации | нет | нет | нет | да | да** |
| может создавать функции | да | да | да | да | да |
| может создавать примитивы | да | нет | да | да | да |
* за счет немедленной инициализации с использованием оператора new напрямую
** объект сервиса недоступен на фазе конфигурации, но экземпляр провайдера доступен (см. пример unicornLauncherProvider выше).
© 2010–2018 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://code.angularjs.org/1.7.8/docs/guide/providers