Spec-Zone.ru › Angular.js 1.6

Улучшить эту документациюПоставщики

Каждое веб-приложение, которое вы создаёте, состоит из объектов, которые взаимодействуют, чтобы что-то делать. Эти объекты необходимо инициализировать и связать друг с другом, чтобы приложение работало. В приложениях 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 — самый сложный тип рецепта. Вам он не нужен, если вы не создаёте многократно используемый фрагмент кода, которому требуется глобальная настройка.
  • Все объекты специального назначения, кроме контроллера, определяются с помощью рецептов фабрики.
Функции / Тип рецепта Фабрика Сервис Значение Константа Провайдер
могут иметь зависимости да да нет нет да
использует дружественный к типу инъекцию нет да да* да* нет
объект доступен на стадии конфигурации нет нет нет да да**
может создавать функции да да да да да
может создавать примитивы да нет да да да

* ценой немедленной инициализации, используя оператор new, напрямую

** объект сервиса недоступен на стадии конфигурации, но экземпляр провайдера доступен (см. пример unicornLauncherProvider выше).

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

Spec-Zone.ru

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