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          |          |            |           |         |        |          |         |       |
+----------------+----------+------------+-----------+---------+--------+----------+---------+-------+
4

3 回答 3

393

与其只是用“是”和“否”填写表格而不做任何解释,我将更详细地介绍一下。

[注意,完成后补充:这最终......比我预期的要长很多。底部有一个 tl;dr,但我希望这能提供信息。]

[此答案也已添加到 AngularJS wiki:了解依赖注入]


提供者 ( $provide)

$provide服务负责告诉 Angular 如何创建新的可注入事物;这些东西叫做服务。服务由称为providers的东西定义,这是您在使用时创建的东西$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$getfactoryvaluegreetingvalue

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您可能想知道为什么有人会费心用if factoryvalue等方法来设置一个成熟的提供程序。答案是提供者允许进行大量配置。我们已经提到,当您通过提供者(或 Angular 提供的任何快捷方式)创建服务时,您会创建一个新的提供者来定义该服务的构建方式。我没有提到的是,这些提供程序可以注入到config您的应用程序的各个部分中,以便您可以与它们进行交互!

首先,Angular 分两个阶段运行您的应用程序 -configrun阶段。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(...),您实际上是在访问该服务的 provider,就像在上一节中一样。

例如,当您定义这样的控制器时:

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

你实际上在做的是这样的:

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

稍后,当 Angular 需要创建控制器的实例时,它会使用该$controller服务(该服务又使用$injector调用您的控制器函数,因此它也注入了它的依赖项)。

过滤器和指令

filterdirective工作方式与controller; filter使用一个名为的服务$filter及其提供者$filterProvider,而directive使用一个名为的服务$compile及其提供者$compileProvider。一些链接:

根据其他示例,myMod.filter并且myMod.directive是配置这些服务的快捷方式。


tl;博士

因此,总而言之,任何被调用的函数$injector.invoke 都可以注入到. 这包括,从您的图表(但不限于):

  • 控制器
  • 指示
  • 工厂
  • 筛选
  • 提供者$get(将提供者定义为对象时)
  • 提供者函数(将提供者定义为构造函数时)
  • 服务

提供者创建可以注入到事物中的新服务。这包括:

  • 持续的
  • 工厂
  • 提供者
  • 服务
  • 价值

也就是说,内置服务可以$controller注入,并且您可以使用这些服务来获取您使用这些方法定义的新过滤器和控制器(即使您定义的东西本身不能注入事物中)。$filter

除此之外,任何注入器调用的函数都可以被注入任何提供者提供的服务——没有限制(除了这里列出的config和差异之外)。run

于 2013-05-30T06:21:38.723 回答
13

BinaryMuse 在她关于供应商、工厂和服务的惊人回答中提出的观点都是极其重要的。

下面是一张我认为可以直观地说明她的观点的图片:

AngularJS 他们都只是提供者
(来源:simplygoodcode.com

于 2015-11-19T20:20:53.147 回答
7

米歇尔的回答很好。我只想指出可以注入指令。如果您有一个名为 的指令myThing,您可以将其注入myThingDirective这是一个人为的示例

上面的例子不是很实用,但是当你想装饰指令时,注入指令的能力很有用。

于 2014-05-19T00:40:14.707 回答