Какие "вещи" можно вводить другим в Angular.js?

У меня есть немного трудное понимание Injection Dependency в Angular. Поэтому мой вопрос: может ли кто-нибудь объяснить, какие из "типов", таких как Controller, Factory, Provider и т.д., Мы можем вводить другим, включая другие экземпляры одного и того же типа?

То, что я действительно ищу, - это таблица, заполненная y/n. Для ячеек с одинаковой строкой/столбцом это означает введение значения одного "типа" в другой, с тем же "типом"

+----------------+----------+------------+-----------+---------+--------+----------+---------+-------+
| Can we inject? | Constant | Controller | Directive | Factory | Filter | Provider | Service | Value |
+----------------+----------+------------+-----------+---------+--------+----------+---------+-------+
| Constant       |          |            |           |         |        |          |         |       |
| Controller     |          |            |           |         |        |          |         |       |
| Directive      |          |            |           |         |        |          |         |       |
| Factory        |          |            |           |         |        |          |         |       |
| Filter         |          |            |           |         |        |          |         |       |
| Provider       |          |            |           |         |        |          |         |       |
| Service        |          |            |           |         |        |          |         |       |
| Value          |          |            |           |         |        |          |         |       |
+----------------+----------+------------+-----------+---------+--------+----------+---------+-------+

Ответ 1

Вместо того, чтобы просто заполнить таблицу словами "да" и "нет" без объяснения причин, я углублюсь в некоторые подробности.

[Примечание, добавлено после окончания: это оказалось... немного длиннее, чем я ожидал. Там внизу д-р, но я надеюсь, что это окажется информационным.]

[Этот ответ также был добавлен в вики AngularJS: Понимание внедрения зависимостей]


Поставщик ($provide)

Служба $provide отвечает за Angular, как создавать новые инъекционные вещи; эти вещи называются сервисами. Услуги определяются провайдерами, которые вы создаете, когда используете $provide. Определение провайдера выполняется с помощью метода provider в службе $provide, и вы можете воспользоваться услугой $provide, запросив ее внедрение в функцию приложения config. Примером может быть что-то вроде этого:

app.config(function($provide) {
  $provide.provider('greeting', function() {
    this.$get = function() {
      return function(name) {
        alert("Hello, " + name);
      };
    };
  });
});

Здесь мы определили нового провайдера для службы под названием greeting; мы можем внедрить переменную с именем greeting в любую инъецируемую функцию (например, контроллеры, подробнее об этом позже), и Angular вызовет функцию провайдера $get для возврата нового экземпляра службы. В этом случае будет введена функция, которая принимает параметр name и alert сообщение, основанное на имени. Мы можем использовать это так:

app.controller('MainController', function($scope, greeting) {
  $scope.onClick = function() {
    greeting('Ford Prefect');
  };
});

Теперь вот хитрость. factory, service и value - это всего лишь ярлыки для определения различных частей поставщика, то есть они предоставляют средство определения поставщика без необходимости наберите все эти вещи. Например, вы могли бы написать этого точно такого же провайдера так:

app.config(function($provide) {
  $provide.factory('greeting', function() {
    return function(name) {
      alert("Hello, " + name);
    };
  });
});

Это важно понимать, поэтому я перефразирую: под капотом AngularJS вызывает для нас точно такой же код, который мы написали выше (версия $provide.provider). Там буквально, 100% нет разницы в двух версиях. value работает точно так же - если все, что мы вернем из нашей функции $get (она же наша функция factory), всегда одинаково, мы можем написать еще меньше кода, используя value. Например, поскольку мы всегда возвращаем одну и ту же функцию для нашего сервиса greeting, мы можем использовать и value для ее определения:

app.config(function($provide) {
  $provide.value('greeting', function(name) {
    alert("Hello, " + name);
  });
});

Опять же, это на 100% идентично двум другим методам, которые мы использовали для определения этой функции - это всего лишь способ сохранить некоторую печать.

Теперь вы, вероятно, заметили эту раздражающую вещь app.config(function($provide) { ... }), которую я использовал. Поскольку определение новых провайдеров (с помощью любого из приведенных выше методов) является очень распространенным явлением, AngularJS предоставляет методы $provider непосредственно для объекта модуля, чтобы сохранить еще больше операций ввода:

var myMod = angular.module('myModule', []);

myMod.provider("greeting", ...);
myMod.factory("greeting", ...);
myMod.value("greeting", ...);

Все они делают то же самое, что и более подробные версии app.config(...), которые мы использовали ранее.

Единственный инъекционный препарат, который я пропустил, - это constant. Пока достаточно просто сказать, что он работает так же, как value. Мы увидим там одно отличие позже.

Чтобы просмотреть, все эти фрагменты кода выполняют одно и то же:

myMod.provider('greeting', function() {
  this.$get = function() {
    return function(name) {
      alert("Hello, " + name);
    };
  };
});

myMod.factory('greeting', function() {
  return function(name) {
    alert("Hello, " + name);
  };
});

myMod.value('greeting', function(name) {
  alert("Hello, " + name);
});

Инжектор ($injector)

Инжектор отвечает за фактическое создание экземпляров наших сервисов с использованием кода, который мы предоставили через $provide (без каламбура). Каждый раз, когда вы пишете функцию, которая принимает введенные аргументы, вы видите инжектор на работе. Каждое приложение AngularJS имеет один $injector, который создается при первом запуске приложения; вы можете получить его, введя $injector в любую инъекционную функцию (да, $injector знает, как внедрить себя!)

Получив $injector, вы можете получить экземпляр определенной службы, вызвав для нее get с именем службы. Например,

var greeting = $injector.get('greeting');
greeting('Ford Prefect');

Инжектор также отвечает за внедрение услуг в функции; например, вы можете волшебным образом внедрить сервисы в любую функцию, которую вы используете, используя метод инжектора invoke;

var myFunction = function(greeting) {
  greeting('Ford Prefect');
};
$injector.invoke(myFunction);

Стоит отметить, что инжектор будет создавать экземпляр службы только один раз. Затем он кэширует все, что провайдер возвращает по имени службы; в следующий раз, когда вы запросите услугу, вы фактически получите тот же объект.

Таким образом, чтобы ответить на ваш вопрос, вы можете внедрить службы в любую функцию, которая вызывается с помощью $injector.invoke. Это включает в себя

  • функции определения контроллера
  • функции определения директивы
  • функции определения фильтра
  • методы $get провайдеров (или функции определения factory)

Поскольку constant и value всегда возвращают статическое значение, они не вызываются через инжектор, и, следовательно, вы не можете вводить их чем-либо.

Настройка провайдеров

Вы можете задаться вопросом, почему кому-то нужно создавать полноценного провайдера с методом provide, если factory, value и т.д. Намного проще. Ответ заключается в том, что провайдеры позволяют много конфигурации. Мы уже упоминали, что когда вы создаете сервис через провайдера (или любой из сочетаний клавиш, предоставляемых Angular), вы создаете нового провайдера, который определяет, как создается этот сервис. Я не упомянул, что эти провайдеры могут быть внедрены в разделы config вашего приложения, чтобы вы могли взаимодействовать с ними!

Во-первых, Angular запускает ваше приложение в два этапа - этапы config и run. На этапе config, как мы видели, вы можете настроить любых поставщиков по мере необходимости. Здесь также устанавливаются директивы, контроллеры, фильтры и тому подобное. На этапе run, как вы можете догадаться, Angular фактически компилирует ваш DOM и запускает ваше приложение.

Вы можете добавить дополнительный код, который будет запускаться на этих этапах, с помощью функций myMod.config и myMod.run - каждая из них принимает функцию для запуска на этой конкретной фазе. Как мы видели в первом разделе, эти функции являются инъекционными - мы внедрили встроенный сервис $provide в наш самый первый пример кода. Однако следует отметить, что на этапе config могут быть введены только поставщики (за исключением услуг в AUTO module-- $provide и $injector).

Например, следующее не допускается:

myMod.config(function(greeting) {
  // WON'T WORK -- greeting is an *instance* of a service.
  // Only providers for services can be injected in config blocks.
});

У вас есть доступ к любым поставщикам услуг, которые вы сделали:

myMod.config(function(greetingProvider) {
  // a-ok!
});

Есть одно важное исключение: constant, поскольку они не могут быть изменены, разрешено вводить внутри блоков config (в этом их отличие от value). Доступ к ним осуществляется только по их имени (суффикс Provider не требуется).

Всякий раз, когда вы определяете поставщика для службы, этот поставщик получает имя serviceProvider, где service - это имя службы. Теперь мы можем использовать возможности провайдеров делать более сложные вещи!

myMod.provider('greeting', function() {
  var text = 'Hello, ';

  this.setText = function(value) {
    text = value;
  };

  this.$get = function() {
    return function(name) {
      alert(text + name);
    };
  };
});

myMod.config(function(greetingProvider) {
  greetingProvider.setText("Howdy there, ");
});

myMod.run(function(greeting) {
  greeting('Ford Prefect');
});

Теперь у нашего провайдера есть функция setText, которую мы можем использовать для настройки нашего alert; мы можем получить доступ к этому провайдеру в блоке config для вызова этого метода и настройки сервиса. Когда мы наконец запустим наше приложение, мы сможем получить службу greeting и попробовать ее, чтобы убедиться, что наша настройка вступила в силу.

Так как это более сложный пример, здесь рабочая демонстрация: http://jsfiddle.net/BinaryMuse/9GjYg/

Контроллеры ($controller)

Функции контроллера могут быть внедрены, но сами контроллеры не могут быть внедрены в другие вещи. Это потому, что контроллеры не создаются через провайдера. Вместо этого есть встроенная служба Angular, которая называется $controller и отвечает за настройку ваших контроллеров. Когда вы звоните myMod.controller(...), вы фактически получаете доступ к этому поставщику услуг, как и в предыдущем разделе.

Например, когда вы определяете контроллер следующим образом:

myMod.controller('MainController', function($scope) {
  // ...
});

Что вы на самом деле делаете, так это:

myMod.config(function($controllerProvider) {
  $controllerProvider.register('MainController', function($scope) {
    // ...
  });
});

Позже, когда Angular необходимо создать экземпляр вашего контроллера, он использует сервис $controller (который, в свою очередь, использует $injector для вызова функции вашего контроллера, поэтому он также вводит свои зависимости).

Фильтры и директивы

filter и directive работают точно так же, как controller; filter использует сервис под названием $filter и его провайдер $filterProvider, в то время как directive использует сервис под названием $compile и его провайдер $compileProvider. Некоторые ссылки:

Как и в других примерах, myMod.filter и myMod.directive являются ярлыками для настройки этих служб.


tl;dr

Итак, подведем итог: любая функция, которая вызывается с помощью $injector.invoke , может быть внедрена в. Это включает в себя, из вашего графика (но не ограничиваясь):

  • контроллер
  • директива
  • завод
  • фильтр
  • провайдер $get (при определении провайдера как объекта)
  • функция провайдера (при определении провайдера как функции конструктора)
  • служба

Поставщик создает новые услуги , которые можно внедрять в вещи. Это включает в себя:

  • постоянная
  • завод
  • поставщик
  • служба
  • значение

Тем не менее, встроенные службы, такие как $controller и $filter, могут быть внедрены, и вы можете использовать эти службы для получения доступа к новым фильтрам и контроллерам, которые вы определили с помощью этих методов (даже если вещи, которые вы определили, не сами по себе, могут быть введены в вещи).

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

Ответ 2

В своем удивительном ответе BinaryMuse говорит о том, что поставщики, фабрики и сервисы - это одно и то же, что чрезвычайно важно.

Ниже приведено изображение, которое, я думаю, может наглядно проиллюстрировать ее точку зрения:

AngularJS they are all just providers
(источник: simplygoodcode.com)

Ответ 3

Отличный ответ Мишель. Я просто хочу указать, что директивы могут быть введены. Если у вас есть директива с именем myThing, вы можете ввести ее с помощью myThingDirective: Вот надуманный пример.

Приведенный выше пример не очень практичен, однако способность вводить директиву полезна, когда вы хотите украсить эту директиву.