Spec-Zone.ru › Angular.js 1.5

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

Каждое веб-приложение, которое вы создаете, состоит из объектов, которые взаимодействуют, чтобы что-то сделать. Эти объекты необходимо инициализировать и связать друг с другом, чтобы приложение работало. В приложениях Angular большинство этих объектов инициализируются и связываются автоматически службой инжектором.

Инжектор создает два типа объектов: службы и специализированные объекты.

Службы — это объекты, API которых определяет разработчик, создающий службу.

Специализированные объекты соответствуют определенному API фреймворка Angular. Эти объекты могут быть контроллерами, директивами, фильтрами или анимациями.

Инжектору необходимо знать, как создавать эти объекты. Вы сообщаете ему об этом, зарегистрировав «рецепт» создания вашего объекта в инжекторе. Существует пять типов рецептов.

Самый подробный, но и самый исчерпывающий — это рецепт провайдера. Остальные четыре типа рецептов — Value, Factory, Service и Constant — представляют собой лишь синтаксический сахар поверх рецепта провайдера.

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

Примечание: несколько слов о модулях

Чтобы инжектор знал, как создавать и связывать все эти объекты, ему нужна регистрация «рецептов». Каждый рецепт имеет идентификатор объекта и описание того, как создать этот объект.

Каждый рецепт принадлежит модулю Angular. Модуль Angular — это контейнер, который содержит один или несколько рецептов. И поскольку ручное отслеживание зависимостей модулей — не из приятных занятий, модуль также может содержать информацию о зависимостях от других модулей.

Когда приложение Angular запускается с заданным модулем приложения, Angular создает новый экземпляр инжектора, который, в свою очередь, создает реестр рецептов как объединение всех рецептов, определенных в ядре «ng», модуле приложения и его зависимостях. Затем инжектор обращается к реестру рецептов, когда ему нужно создать объект для вашего приложения.

Рецепт Value

Предположим, что мы хотим создать очень простую службу под названием «clientId», которая предоставляет строку, представляющую идентификатор аутентификации, используемый для некоторого удаленного API. Вы определите ее так:

var myApp = angular.module('myApp', []);
myApp.value('clientId', 'a12345654321x');

Обратите внимание, как мы создали модуль Angular под названием myApp, и указали, что это определение модуля содержит «рецепт» для построения службы clientId, которая в данном случае представляет собой простую строку.

И вот как вы можете отобразить её с помощью привязки данных Angular:

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 для определения значения, которое будет предоставлено при запросе службы с идентификатором «clientId».

Переходим к более сложным примерам!

Рецепт Factory

Рецепт Value очень прост в написании, но ему не хватает важных функций, которые нам часто нужны при создании служб. Теперь давайте рассмотрим более мощного брата рецепта Value — рецепт Factory. Рецепт Factory добавляет следующие возможности:

  • возможность использовать другие службы (иметь зависимости)
  • инициализация службы
  • отложенная/ленивая инициализация

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

Примечание: все службы в Angular являются синглтонами. Это означает, что инжектор использует каждый рецепт не более одного раза для создания объекта. Затем инжектор кэширует ссылку для всех будущих нужд.

Поскольку 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]);

Гораздо проще!

Примечание: Да, мы назвали один из наших рецептов служб «Service». Нам жаль это, и мы знаем, что будем как-то наказаны за наш проступок. Это как назвать одного из наших отпрысков «Ребенок». Ой, это бы доставило хлопот учителям.

Рецепт 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);
}]);

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

Во время загрузки приложения, прежде чем Angular начнёт создавать все службы, он настраивает и инициализирует все провайдеры. Мы называем эту фазу жизненного цикла приложения фазой конфигурации. На этой стадии службы недоступны, потому что они ещё не созданы.

После завершения фазы конфигурации взаимодействие с провайдерами запрещено, и начинается процесс создания служб. Мы называем эту часть жизненного цикла приложения фазой выполнения.

Рецепт Constant

Мы только что узнали, как Angular разделяет жизненный цикл на фазу конфигурации и фазу выполнения, и как можно предоставить конфигурацию вашему приложению через функцию конфигурации. Поскольку функция конфигурации выполняется в фазе конфигурации, когда никакие службы недоступны, она не имеет доступа даже к простым объектам значений, созданным с помощью рецепта 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>

Объекты специального назначения

Ранее мы упомянули, что у нас также есть объекты специального назначения, которые отличаются от служб. Эти объекты расширяют фреймворк как плагины и, следовательно, должны реализовывать интерфейсы, определенные Angular. Эти интерфейсы — 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>

Используя рецепты фабрики, вы также можете определить фильтры и анимации Angular, но контроллеры немного особенные. Вы создаете контроллер как пользовательский тип, объявляя его зависимости как аргументы для функции-конструктора. Затем этот конструктор регистрируется в модуле. Давайте рассмотрим DemoController, созданный в одном из ранних примеров:

myApp.controller('DemoController', ['clientId', function DemoController(clientId) {
  this.clientId = clientId;
}]);

DemoController инициализируется через его конструктор каждый раз, когда приложению требуется экземпляр DemoController (в нашем простом приложении это происходит только один раз). Таким образом, в отличие от сервисов, контроллеры не являются синглтонами. Конструктор вызывается со всеми запрошенными сервисами, в нашем случае сервисом clientId.

Заключение

Чтобы подвести итог, давайте резюмируем самые важные моменты:

  • Инжектор использует рецепты для создания двух типов объектов: сервисы и объекты специального назначения
  • Существует пять типов рецептов, определяющих, как создавать объекты: Значение, Фабрика, Сервис, Провайдер и Константа.
  • Фабрика и Сервис являются наиболее часто используемыми рецептами. Единственное различие между ними заключается в том, что рецепт Сервис лучше подходит для объектов пользовательского типа, в то время как Фабрика может производить примитивы и функции JavaScript.
  • Рецепт Провайдер является основным типом рецепта, а все остальные – всего лишь синтаксический сахар над ним.
  • Провайдер – это самый сложный тип рецепта. Вам он не нужен, если вы не разрабатываете многократно используемый фрагмент кода, которому нужна глобальная конфигурация.
  • Все объекты специального назначения, кроме контроллера, определяются с помощью рецептов фабрики.
Функции / Тип рецепта Фабрика Сервис Значение Константа Провайдер
может иметь зависимости да да нет нет да
использует тип-дружественный инъекции нет да да* да* нет
объект доступен на фазе конфигурации нет нет нет да да**
может создавать функции да да да да да
может создавать примитивы да нет да да да

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

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

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

Spec-Zone.ru

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