¿Qué “cosas” se pueden inyectar a otros en Angular.js?

Me está costando un poco entender la Inyección de Dependencia en Angular. Entonces, mi pregunta es, ¿puede alguien explicar cuál de los “tipos”, como Controller, Factory, Provider, etc., podemos inyectar en otros, incluidas otras instancias del mismo “tipo”?

Lo que estoy buscando en realidad es esta tabla llena de y / n. Para las celdas con la misma fila / columna, eso significa inyectar el valor de un “tipo” en otro con el mismo “tipo”

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

En lugar de simplemente completar la tabla con “sí” y “no” sin explicación, entraré un poco más en detalle.

[Nota, agregado después de terminar: esto terminó siendo … bastante más largo de lo que esperaba. Hay un tl; dr en la parte inferior, pero espero que esto sea informativo]

[Esta respuesta también se ha agregado a la wiki de AngularJS: Entendiendo la Inyección de Dependencia ]


El proveedor ( $provide )

El servicio $provide es responsable de decirle a Angular cómo crear nuevas cosas inyectables; estas cosas se llaman servicios . Los servicios se definen por elementos llamados proveedores , que es lo que está creando cuando usa $provide . La definición de un proveedor se realiza a través del método de provider en el servicio $provide , y puede obtener el servicio $provide solicitando que se lo inyecte a la función de config una aplicación. Un ejemplo podría ser algo como esto:

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

Aquí hemos definido un nuevo proveedor para un servicio llamado greeting ; podemos inyectar una variable llamada greeting en cualquier función inyectable (como controladores, más sobre eso más adelante) y Angular llamará a la función $get del proveedor para devolver una nueva instancia del servicio. En este caso, lo que se inyectará es una función que toma un parámetro de name y alert un mensaje basado en el nombre. Podríamos usarlo así:

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

Ahora aquí está el truco. factory , el service y el value son solo accesos directos para definir varias partes de un proveedor; es decir, proporcionan un medio para definir un proveedor sin tener que escribir todo eso. Por ejemplo, podría escribir ese mismo proveedor exactamente así:

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

Es importante entenderlo, así que voy a reformular: bajo el capó, AngularJS está llamando exactamente el mismo código que escribimos arriba (la versión $provide.provider ) para nosotros. Literalmente, el 100% no tiene diferencias en las dos versiones. value funciona de la misma manera: si lo que devolvemos de nuestra función $get (también conocida como nuestra función de factory ) siempre es exactamente igual, podemos escribir aún menos código utilizando el value . Por ejemplo, dado que siempre devolvemos la misma función para nuestro servicio de greeting , podemos usar el value para definirlo también:

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

De nuevo, esto es 100% idéntico a los otros dos métodos que hemos usado para definir esta función: es solo una manera de guardar algo de tipeo.

Ahora probablemente hayas notado esta molesta app.config(function($provide) { ... }) cosa que he estado usando. Como la definición de nuevos proveedores (a través de cualquiera de los métodos proporcionados anteriormente) es tan común, AngularJS expone los métodos de $provider directamente en el objeto del módulo, para ahorrar aún más tipeo:

 var myMod = angular.module('myModule', []); myMod.provider("greeting", ...); myMod.factory("greeting", ...); myMod.value("greeting", ...); 

Todos hacen lo mismo que las versiones más detalladas de app.config(...) que utilizamos anteriormente.

El inyectable que me salté hasta ahora es constant . Por ahora, es bastante fácil decir que funciona igual que el value . Veremos que hay una diferencia más adelante.

Para revisar , todas estas piezas de código están haciendo exactamente lo mismo:

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

El inyector ( $injector )

El inyector es responsable de crear instancias de nuestros servicios utilizando el código que proporcionamos a través de $provide (sin juego de palabras). Cada vez que escribe una función que toma argumentos inyectados, está viendo que el inyector funciona. Cada aplicación AngularJS tiene un $injector único que se crea cuando se inicia la aplicación por primera vez; puede obtenerlo inyectando $injector en cualquier función inyectable (sí, $injector sabe cómo inyectarse).

Una vez que tiene $injector , puede obtener una instancia de un servicio definido llamando al get el nombre del servicio. Por ejemplo,

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

El inyector también es responsable de inyectar servicios en funciones; por ejemplo, puede inyectar mágicamente servicios en cualquier función que tenga utilizando el método de invoke del inyector;

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

Vale la pena señalar que el inyector solo creará una instancia de un servicio una vez . Luego guarda en caché lo que devuelva el proveedor por el nombre del servicio; la próxima vez que solicite el servicio, obtendrá exactamente el mismo objeto.

Entonces, para responder a su pregunta, puede inyectar servicios en cualquier función que se llame con $injector.invoke . Esto incluye

  • funciones de definición del controlador
  • funciones de definición de directivas
  • funciones de definición de filtro
  • los $get métodos de proveedores (también conocido como las funciones de definición de factory )

Como constant s y los value s siempre devuelven un valor estático, no se invocan a través del inyector y, por lo tanto, no se puede inyectar con nada.

Configurando proveedores

Es posible que se pregunte por qué alguien se molestaría en configurar un proveedor completo con el método de provide si la factory , el value , etc. son mucho más fáciles. La respuesta es que los proveedores permiten una gran cantidad de configuración. Ya hemos mencionado que cuando crea un servicio a través del proveedor (o cualquiera de los accesos directos que Angular le brinda), crea un nuevo proveedor que define cómo se construye ese servicio. Lo que no mencioné es que estos proveedores pueden ser inyectados en secciones de config de su aplicación para que pueda interactuar con ellos.

Primero, Angular ejecuta su aplicación en dos fases: las fases de config y run . La fase de config , como hemos visto, es donde puede configurar proveedores según sea necesario. Aquí también se configuran directivas, controladores, filtros y demás. La fase de run , como se puede adivinar, es donde Angular realmente comstack su DOM e inicia su aplicación.

Puede agregar código adicional para ejecutar en estas fases con las funciones myMod.config y myMod.run ; cada una de ellas toma una función para ejecutarse durante esa fase específica. Como vimos en la primera sección, estas funciones son inyectables: inyectamos el servicio $provide incorporado en nuestra primera muestra de código. Sin embargo, lo que vale la pena observar es que durante la fase de config , solo los proveedores pueden ser inyectados (con la excepción de los servicios en el módulo AUTO– $provide y $injector ).

Por ejemplo, lo siguiente no está permitido :

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

A lo que tienes acceso es a cualquier proveedor de servicios que hayas hecho:

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

Hay una excepción importante: las constant s, dado que no se pueden modificar, se pueden inyectar dentro de bloques de config (así es como difieren del value s). Se accede solo por su nombre (no es necesario el sufijo Provider ).

Cada vez que definió un proveedor para un servicio, ese proveedor recibe el nombre serviceProvider , donde el service es el nombre del servicio. ¡Ahora podemos usar el poder de los proveedores para hacer cosas más complicadas!

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

Ahora tenemos una función en nuestro proveedor llamada setText que podemos usar para personalizar nuestra alert ; podemos obtener acceso a este proveedor en un bloque de config para llamar a este método y personalizar el servicio. Cuando finalmente ejecutamos nuestra aplicación, podemos tomar el servicio de greeting y probarlo para ver que nuestra personalización surta efecto.

Dado que este es un ejemplo más complejo, aquí hay una demostración de trabajo: http://jsfiddle.net/BinaryMuse/9GjYg/

Controladores ( $controller )

Las funciones del controlador pueden ser inyectadas, pero los controladores mismos no pueden ser inyectados en otras cosas. Eso es porque los controladores no se crean a través del proveedor. En cambio, hay un servicio angular $controller llamado $controller que se encarga de configurar sus controladores. Cuando llama a myMod.controller(...) , en realidad está accediendo al proveedor de este servicio , al igual que en la última sección.

Por ejemplo, cuando defines un controlador como este:

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

Lo que en realidad estás haciendo es esto:

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

Más tarde, cuando Angular necesita crear una instancia de su controlador, usa el servicio $controller (que a su vez usa $injector para invocar la función de su controlador para que también se inyecte sus dependencias).

Filtros y directivas

filter y la directive funcionan exactamente de la misma manera que el controller ; filter usa un servicio llamado $filter y su proveedor $filterProvider , mientras que la directive usa un servicio llamado $compile y su proveedor $compileProvider . Algunos enlaces:

Según los otros ejemplos, myMod.filter y myMod.directive son accesos directos a la configuración de estos servicios.


tl; dr

Entonces, para resumir, cualquier función que se llame con $injector.invoke puede ser inyectada . Esto incluye, desde su tabla (pero no está limitado a):

  • controlador
  • directiva
  • fábrica
  • filtrar
  • proveedor $get (cuando se define el proveedor como un objeto)
  • función de proveedor (cuando se define el proveedor como una función de constructor)
  • Servicio

El proveedor crea nuevos servicios que pueden inyectarse en cosas . Esto incluye:

  • constante
  • fábrica
  • proveedor
  • Servicio
  • valor

Dicho esto, se pueden inyectar servicios $controller como $controller y $filter , y puede usar esos servicios para obtener los nuevos filtros y controladores que definió con esos métodos (aunque las cosas que definió no son, por sí mismos) , capaz de ser inyectado en cosas).

Aparte de eso, cualquier función invocada por un inyector puede inyectarse con cualquier servicio proporcionado por el proveedor, no existe ninguna restricción (aparte de las diferencias de config y run aquí enumeradas).

El punto que BinaryMuse hace en su asombrosa respuesta sobre proveedores, fábricas y servicios, todos siendo lo mismo extremadamente importante.

A continuación se muestra una imagen que creo que puede ilustrar visualmente su punto:

AngularJS todos son solo proveedores http://www.simplygoodcode.com/wp-content/uploads/2015/11/angularjs-provider-service-factory-highlight.png

Gran respuesta de Michelle. Solo quiero señalar que se pueden inyectar directivas . Si tiene una directiva llamada myThing , puede inyectarla con myThingDirective : Aquí hay un ejemplo artificial .

El ejemplo anterior no es muy práctico, sin embargo, la capacidad de inyectar una directiva es útil cuando desea decorar esa directiva .