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

142

Мне сложно понять внедрение зависимостей в 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          |          |            |           |         |        |          |         |       |
+----------------+----------+------------+-----------+---------+--------+----------+---------+-------+
user1527166
источник

Ответы:

392

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

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

[Этот ответ также был добавлен в вики 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функция определения)

Поскольку constants и values всегда возвращают статическое значение, они не вызываются через инжектор, и поэтому вы не можете вводить их ничем.

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

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

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

Вы можете добавить дополнительный код , который будет работать в этих фазах с myMod.configи myMod.runфункциями - каждый берет функцию для выполнения на этой конкретной стадии. Как мы видели в первом разделе, эти функции являются инъекционными - мы внедрили встроенную $provideслужбу в наш самый первый пример кода. Однако стоит отметить, что на configэтапеAUTO внедрения могут быть введены только провайдеры (за исключением сервисов в модуле - $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!
});

Есть одно важное исключение: constants, поскольку они не могут быть изменены, разрешено вводить внутрь configблоков (этим они отличаются от values). Доступ к ним можно получить только по имени ( 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различий , перечисленных в данном описании).

Мишель Тилли
источник
6
Вот это да! спасибо, что нашли время ответить так подробно! Я прочитал это дважды и, кажется, довольно много понял. Позже сегодня я подробно изучу его и ссылки, которые вы дали. И еще +1 за кота. :)
user1527166
18
Один из самых полезных и подробных ответов SO, которые я когда-либо встречал - спасибо!
Godders 06
11
Этот ответ определяет новый уровень потрясающего. Освещение.
Ngure Nyaga 09
4
Безусловно, лучший ресурс для AngularJS, с которым я когда-либо сталкивался. Спасибо.
code90
5
Буквально лучшая часть документации AngularJS, которую я когда-либо видел. Вперед!
Iain Duncan
13

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

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

AngularJS они все просто провайдеры
(источник: simplegoodcode.com )

Луис Перес
источник
7

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

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

Гил Бирман
источник
Похоже, что второй пример для украшения этой директивы не работает с Angular 1.4. (см. комментарий Хуана Бискайи )
Vadorequest