Quelles «choses» peuvent être injectées dans d'autres dans Angular.js?


142

J'ai un peu de mal à comprendre l'injection de dépendances dans Angular. Ma question est donc la suivante: quelqu'un peut-il expliquer lequel des "types", comme Controller, Factory, Provider, etc. pouvons-nous injecter dans d'autres, y compris d'autres instances du même "type"?

Ce que je recherche en fait, c'est ce tableau rempli de y / n. Pour les cellules avec la même ligne / colonne, cela signifie injecter la valeur d'un "type" dans un autre avec le même "type"

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

Réponses:


391

Plutôt que de remplir le tableau avec «oui» et «non» sans aucune explication, je vais entrer un peu plus en détail.

[Remarque, ajouté après avoir terminé: cela a fini par être ... un peu plus long que prévu. Il y a un tl; dr en bas, mais j'espère que cela sera informatif.]

[Cette réponse a également été ajoutée au wiki AngularJS: Understanding Dependency Injection ]


Le fournisseur ( $provide)

Le $provideservice est chargé de dire à Angular comment créer de nouvelles choses injectables; ces choses s'appellent des services . Les services sont définis par des éléments appelés fournisseurs , ce que vous créez lorsque vous utilisez $provide. La définition d'un fournisseur se fait via la providerméthode sur le $provideservice, et vous pouvez mettre la main sur le $provideservice en demandant qu'il soit injecté dans la configfonction d' une application . Un exemple pourrait être quelque chose comme ceci:

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

Ici, nous avons défini un nouveau fournisseur pour un service appelé greeting; nous pouvons injecter une variable nommée greetingdans n'importe quelle fonction injectable (comme les contrôleurs, plus à ce sujet plus tard) et Angular appellera la $getfonction du fournisseur afin de renvoyer une nouvelle instance du service. Dans ce cas, la chose qui sera injectée est une fonction qui prend un nameparamètre et alertun message basé sur le nom. Nous pourrions l'utiliser comme ceci:

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

Voici maintenant le truc. factory, serviceet ne valuesont que des raccourcis pour définir diverses parties d'un fournisseur - c'est-à-dire qu'ils fournissent un moyen de définir un fournisseur sans avoir à taper tout cela. Par exemple, vous pouvez écrire exactement le même fournisseur comme ceci:

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

Il est important de comprendre, donc je vais reformuler: sous le capot, AngularJS appelle exactement le même code que nous avons écrit ci-dessus (la $provide.providerversion) pour nous. Il n'y a littéralement aucune différence entre les deux versions. valuefonctionne exactement de la même manière - si tout ce que nous retournerions de notre $getfonction (c'est-à-dire notre factoryfonction) est toujours exactement le même, nous pouvons écrire encore moins de code en utilisant value. Par exemple, puisque nous retournons toujours la même fonction pour notre greetingservice, nous pouvons également l'utiliser valuepour la définir:

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

Encore une fois, c'est 100% identique aux deux autres méthodes que nous avons utilisées pour définir cette fonction - c'est juste un moyen de sauvegarder un peu de frappe.

Maintenant, vous avez probablement remarqué cette app.config(function($provide) { ... })chose ennuyeuse que j'utilise. Puisque la définition de nouveaux fournisseurs (via l' une des méthodes ci-dessus) est si courante, AngularJS expose les $providerméthodes directement sur l'objet module, pour économiser encore plus de frappe:

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

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

Ceux-ci font tous la même chose que les app.config(...)versions plus verbeuses que nous avons utilisées précédemment.

Le seul injectable que j'ai sauté jusqu'à présent est constant. Pour l'instant, il est assez facile de dire que cela fonctionne exactement comme value. Nous verrons qu'il y a une différence plus tard.

Pour revoir , tous ces morceaux de code font exactement la même chose:

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);
});

L'injecteur ( $injector)

L'injecteur est responsable de la création des instances de nos services en utilisant le code que nous avons fourni via $provide(sans jeu de mots). Chaque fois que vous écrivez une fonction qui prend des arguments injectés, vous voyez l'injecteur au travail. Chaque application AngularJS en a une $injectorqui est créée au premier démarrage de l'application; vous pouvez vous en emparer $injectoren l' injectant dans n'importe quelle fonction injectable (oui, $injectorsait comment s'injecter!)

Une fois que vous l'avez fait $injector, vous pouvez obtenir une instance d'un service défini en l'appelant getavec le nom du service. Par exemple,

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

L'injecteur est également chargé d'injecter des services dans les fonctions; par exemple, vous pouvez par magie injecter des services dans n'importe quelle fonction que vous avez en utilisant la invokeméthode de l'injecteur ;

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

Cela vaut la peine de noter que l'injecteur ne créer une instance d'un service une fois . Il met ensuite en cache tout ce que le fournisseur renvoie par le nom du service; la prochaine fois que vous demanderez le service, vous obtiendrez exactement le même objet.

Donc, pour répondre à votre question, vous pouvez injecter des services dans n'importe quelle fonction appelée avec$injector.invoke . Ceci comprend

  • fonctions de définition du contrôleur
  • fonctions de définition de directive
  • fonctions de définition de filtre
  • les $getméthodes des fournisseurs (aka les factoryfonctions de définition)

Puisque constants et values renvoient toujours une valeur statique, ils ne sont pas appelés via l'injecteur et vous ne pouvez donc pas leur injecter quoi que ce soit.

Configuration des fournisseurs

Vous demandez peut - être pourquoi quelqu'un prendrait la peine de mettre en place un fournisseur à part entière avec la provideméthode si factory, valueetc. sont tellement plus facile. La réponse est que les fournisseurs autorisent beaucoup de configuration. Nous avons déjà mentionné que lorsque vous créez un service via le fournisseur (ou l'un des raccourcis fournis par Angular), vous créez un nouveau fournisseur qui définit la manière dont ce service est construit. Ce que je n'ai pas mentionné, c'est que ces fournisseurs peuvent être injectés dans des configsections de votre application afin que vous puissiez interagir avec eux!

Tout d'abord, Angular exécute votre application en deux phases - les phases configet run. La configphase, comme nous l'avons vu, est celle où vous pouvez configurer tous les fournisseurs si nécessaire. C'est également là que les directives, les contrôleurs, les filtres et autres sont configurés. La runphase, comme vous pouvez le deviner, est celle où Angular compile réellement votre DOM et démarre votre application.

Vous pouvez ajouter du code supplémentaire à exécuter dans ces phases avec les fonctions myMod.configet myMod.run- chacune prend une fonction à exécuter pendant cette phase spécifique. Comme nous l'avons vu dans la première section, ces fonctions sont injectables - nous avons injecté le service intégré $providedans notre tout premier exemple de code. Cependant, il convient de noter que pendant la configphase, seuls les fournisseurs peuvent être injectés (à l'exception des services du AUTOmodule - $provideet $injector).

Par exemple, ce qui suit n'est pas autorisé :

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

Ce que vous n'avez accès à tous sont les fournisseurs de services que vous avez fait:

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

Il y a une exception importante: les constants, puisqu'ils ne peuvent pas être modifiés, peuvent être injectés à l'intérieur de configblocs (c'est ainsi qu'ils diffèrent de values). Ils sont accessibles uniquement par leur nom (pas de Providersuffixe nécessaire).

Chaque fois que vous avez défini un fournisseur pour un service, ce fournisseur est nommé serviceProvider, où serviceest le nom du service. Maintenant, nous pouvons utiliser la puissance des fournisseurs pour faire des choses plus compliquées!

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');
});

Maintenant, nous avons une fonction sur notre fournisseur appelée setTextque nous pouvons utiliser pour personnaliser notre alert; nous pouvons accéder à ce fournisseur dans un configbloc pour appeler cette méthode et personnaliser le service. Lorsque nous exécutons enfin notre application, nous pouvons récupérer le greetingservice et l'essayer pour voir que notre personnalisation a pris effet.

Puisqu'il s'agit d'un exemple plus complexe, voici une démonstration de travail: http://jsfiddle.net/BinaryMuse/9GjYg/

Contrôleurs ( $controller)

Les fonctions du contrôleur peuvent être injectées dans, mais les contrôleurs eux-mêmes ne peuvent pas être injectés dans d'autres choses. C'est parce que les contrôleurs ne sont pas créés via le fournisseur. Au lieu de cela, il existe un service Angular intégré appelé $controllerqui est responsable de la configuration de vos contrôleurs. Lorsque vous appelez myMod.controller(...), vous accédez en fait au fournisseur de ce service , comme dans la dernière section.

Par exemple, lorsque vous définissez un contrôleur comme celui-ci:

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

Voici ce que vous faites réellement:

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

Plus tard, quand Angular a besoin de créer une instance de votre contrôleur, il utilise le $controllerservice (qui à son tour utilise le $injectorpour appeler votre fonction de contrôleur afin qu'il obtienne également ses dépendances injectées).

Filtres et directives

filteret directivefonctionnent exactement de la même manière que controller; filterutilise un service appelé $filteret son fournisseur $filterProvider, tandis directivequ'utilise un service appelé $compileet son fournisseur $compileProvider. Quelques liens:

Comme dans les autres exemples, myMod.filteret myMod.directivesont des raccourcis pour configurer ces services.


tl; dr

Donc, pour résumer, toute fonction appelée avec $injector.invoke peut être injectée dans . Cela inclut, à partir de votre graphique (mais sans s'y limiter):

  • manette
  • directif
  • usine
  • filtre
  • fournisseur $get(lors de la définition du fournisseur comme objet)
  • fonction de fournisseur (lors de la définition du fournisseur comme fonction de constructeur)
  • un service

Le fournisseur crée de nouveaux services qui peuvent être injectés dans les choses . Ceci comprend:

  • constant
  • usine
  • fournisseur
  • un service
  • valeur

Cela dit, les services intégrés aiment $controlleret $filter peuvent être injectés, et vous pouvez utiliser ces services pour obtenir les nouveaux filtres et contrôleurs que vous avez définis avec ces méthodes (même si les éléments que vous avez définis ne sont pas, par eux-mêmes, capables d'être injecté dans les choses).

En dehors de cela, toute fonction invoquée par l'injecteur peut être injectée avec n'importe quel service fourni par le fournisseur - il n'y a aucune restriction (autre que les configet les rundifférences énumérées ici).


6
Hou la la! merci d'avoir pris le temps de répondre avec autant de détails! J'ai lu cela deux fois et je pense avoir bien compris. Je vais l'étudier et les liens que vous avez donnés en détail plus tard dans la journée. Et un autre +1 pour le chat. :)
user1527166

18
L'une des réponses SO les plus utiles et les plus détaillées que j'ai rencontrées - merci!
Godders

11
Cette réponse définit un nouveau niveau de génial. Des trucs lumineux.
Ngure Nyaga

4
De loin la meilleure ressource que j'ai rencontrée pour AngularJS. Merci.
code90

5
Littéralement le meilleur morceau de documentation AngularJS que j'ai vu. Marche à suivre!
Iain Duncan

13

Le point que BinaryMuse fait valoir dans sa réponse étonnante sur les fournisseurs, les usines et les services est extrêmement important.

Voici une image qui, je pense, peut illustrer visuellement son propos:

AngularJS ce ne sont que des fournisseurs
(source: simplygoodcode.com )


7

Excellente réponse de Michelle. Je veux juste souligner que des directives peuvent être injectées. Si vous avez une directive nommée myThing, vous pouvez l'injecter avec myThingDirective: Voici un exemple artificiel .

L'exemple ci-dessus n'est pas très pratique, mais la possibilité d'injecter une directive est utile lorsque vous souhaitez décorer cette directive .


Il semble que le deuxième exemple pour décorer cette directive ne fonctionne pas depuis Angular 1.4. (voir commentaire de Juan Biscaia là-bas)
Vadorequest
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.