Улучшить эту документациюПровайдеры
Каждое веб-приложение, которое вы создаёте, состоит из объектов, которые взаимодействуют друг с другом для выполнения задач. Эти объекты необходимо инициализировать и связать друг с другом, чтобы приложение работало. В приложениях AngularJS большинство этих объектов инициализируются и связываются автоматически службой $инжектор.
Инжектор создаёт два типа объектов: службы и специализированные объекты.
Службы — это объекты, API которых определяет разработчик, создающий службу.
Специализированные объекты соответствуют определённому API фреймворка AngularJS. Эти объекты представляют собой контроллеры, директивы, фильтры или анимации.
Инжектору необходимо знать, как создавать эти объекты. Вы сообщаете об этом, регистрируя «рецепт» создания вашего объекта в инжекторе. Существует пять типов рецептов.
Самый подробный, но также и самый исчерпывающий — это рецепт провайдера. Остальные четыре типа рецептов — Value, Factory, Service и Constant — являются лишь синтаксическим сахаром поверх рецепта провайдера.
Давайте рассмотрим различные сценарии создания и использования служб с помощью различных типов рецептов. Мы начнём с самого простого случая, где в разных частях вашего кода необходима общая строка, и выполним это с помощью рецепта Value.
Примечание: несколько слов о модулях
Для того, чтобы инжектор знал, как создавать и связывать все эти объекты, ему необходим реестр «рецептов». Каждый рецепт имеет идентификатор объекта и описание того, как создать этот объект.
Каждый рецепт принадлежит модулю AngularJS. Модуль AngularJS — это контейнер, который содержит один или несколько рецептов. И поскольку ручное отслеживание зависимостей модулей — не самое приятное занятие, модуль также может содержать информацию о зависимостях от других модулей.
Когда приложение AngularJS запускается с заданным модулем приложения, AngularJS создаёт новый экземпляр инжектора, который, в свою очередь, создаёт реестр рецептов как объединение всех рецептов, определённых в ядре «ng» модуле, модуле приложения и его зависимостях. Затем инжектор обращается к реестру рецептов, когда ему необходимо создать объект для вашего приложения.
Рецепт Value
Предположим, что мы хотим иметь очень простую службу с именем «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>
В этом примере мы использовали рецепт Value для определения значения, которое будет предоставлено, когда DemoController запросит службу с идентификатором «clientId».
Перейдём к более сложным примерам!
Рецепт Factory
Рецепт Value очень прост в написании, но ему не хватает важных функций, которые нам часто необходимы при создании служб. Теперь давайте рассмотрим более мощного брата рецепта Value — рецепт Factory. Рецепт Factory добавляет следующие возможности:
- возможность использовать другие службы (иметь зависимости)
- инициализация службы
- отложенная/ленивая инициализация
Рецепт Factory строит новую службу, используя функцию с нулём или более аргументами (это зависимости от других служб). Возвращаемое значение этой функции — экземпляр службы, созданный этим рецептом.
Примечание: все службы в AngularJS являются синглтонами. Это означает, что инжектор использует каждый рецепт не более одного раза для создания объекта. Затем инжектор кеширует ссылку для всех будущих потребностей.
Поскольку Factory — более мощная версия рецепта Value, с её помощью можно построить ту же самую службу. Используя предыдущий пример рецепта Value clientId, мы можем переписать его как рецепт Factory следующим образом:
myApp.factory('clientId', function clientIdFactory() {
return 'a12345654321x';
});
Но учитывая, что токен — это просто строковая константа, придерживаться рецепта Value всё ещё более целесообразно, поскольку это упрощает понимание кода.
Однако предположим, что нам также нужно создать службу, которая вычисляет токен, используемый для аутентификации по удалённому 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 определена с помощью рецепта Factory, который зависит от службы clientId. Служба фабрики затем использует устойчивый к NSA шифрование для создания токена аутентификации.
<serviceId>Factory (например, apiTokenFactory). Хотя эта конвенция именования не является обязательной, она помогает при навигации по кодовой базе или просмотре трассировок стека в отладчике. Точно так же, как и с рецептом Value, рецепт Factory может создавать службы любого типа, будь то примитив, объект-литерал, функция или даже экземпляр пользовательского типа.
Рецепт Service
Разработчики 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 с помощью рецепта Factory:
myApp.factory('unicornLauncher', ["apiToken", function(apiToken) {
return new UnicornLauncher(apiToken);
}]);
Однако это именно тот случай, когда рецепт Service наиболее подходит.
Рецепт Service производит службу, как и рецепты Value или Factory, но делает это, вызывая конструктор с оператором new. Конструктор может принимать ноль или более аргументов, которые представляют зависимости, необходимые для экземпляра этого типа.
Примечание: рецепты Service следуют паттерну проектирования, называемому инъекцией конструктора.
Поскольку у нас уже есть конструктор для нашего типа UnicornLauncher, мы можем заменить рецепт Factory выше на рецепт Service следующим образом:
myApp.service('unicornLauncher', ["apiToken", UnicornLauncher]);
Гораздо проще!
Рецепт Provider
Как уже упоминалось во введении, рецепт Provider — это основной тип рецепта, а все остальные типы рецептов — всего лишь синтаксический сахар поверх него. Это самый подробный рецепт с наибольшим количеством возможностей, но для большинства служб это избыточно.
Рецепт Provider синтаксически определяется как пользовательский тип, который реализует метод $get. Этот метод — функция фабрики, такая же, как мы используем в рецепте Factory. На самом деле, если вы определите рецепт Factory, пустой тип Provider с методом $get, установленным на вашу функцию фабрики, автоматически создаётся под капотом.
Вы должны использовать рецепт Provider только в том случае, если хотите предоставить 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 начнёт создавать все службы, он настраивает и инициализирует все провайдеры. Мы называем эту фазу жизненного цикла приложения фазой конфигурации. На этой фазе службы недоступны, потому что они ещё не были созданы.
После завершения фазы конфигурации взаимодействие с провайдерами запрещено, и начинается процесс создания служб. Мы называем эту часть жизненного цикла приложения фазой выполнения.
Рецепт Constant
Мы только что узнали, как AngularJS делит жизненный цикл на фазы конфигурации и выполнения и как вы можете предоставить конфигурацию вашему приложению через функцию конфигурации. Поскольку функция конфигурации выполняется в фазе конфигурации, когда никакие службы недоступны, она не имеет доступа даже к простым объектам значений, созданным с помощью рецепта Value.
Поскольку простые значения, такие как префиксы URL, не имеют зависимостей или конфигурации, часто бывает полезно сделать их доступными как в фазе конфигурации, так и в фазе выполнения. Для этого и предназначен рецепт Constant.
Предположим, что наша служба unicornLauncher может нанести отметку на единороге с именем планеты, с которой он запускается, если это имя было предоставлено во время фазы конфигурации. Имя планеты специфично для приложения и также используется различными контроллерами во время выполнения приложения. Мы можем тогда определить имя планеты как константу следующим образом:
myApp.constant('planetName', 'Greasy Giant');
Затем мы можем настроить unicornLauncherProvider следующим образом:
myApp.config(['unicornLauncherProvider', 'planetName', function(unicornLauncherProvider, planetName) {
unicornLauncherProvider.useTinfoilShielding(true);
unicornLauncherProvider.stampText(planetName);
}]);
И поскольку рецепт Constant делает значение доступным во время выполнения, так же как и рецепт Value, мы также можем использовать его в нашем контроллере и шаблоне:
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. Эти интерфейсы — Controller, Directive, Filter и Animation.
Инструкции для инжектора по созданию этих специальных объектов (за исключением объектов Controller) используют рецепт Factory за кулисами.
Давайте посмотрим, как мы создадим очень простой компонент с помощью API директивы, который зависит от константы planetName, которую мы только что определили, и отображает имя планеты, в нашем случае: «Имя планеты: Жирный Гигант».
Поскольку директивы регистрируются с помощью рецепта Factory, мы можем использовать тот же синтаксис, что и с фабриками.
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>
Используя рецепты Factory, вы также можете определять фильтры и анимации 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–2020 Google, Inc.
Licensed under the Creative Commons Attribution License 3.0.
https://code.angularjs.org/1.8.2/docs/guide/providers