¿Qué "cosas" se pueden inyectar en otros en Angular.js?

142

Me está costando un poco entender la inyección de dependencia en angular. Entonces mi pregunta es, ¿alguien puede explicar cuál de los "tipos", como Controlador, Fábrica, Proveedor, etc., podemos inyectar en otros, incluidas otras instancias del mismo "tipo"?

Lo que realmente estoy buscando es esta tabla llena de y / n. Para 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          |          |            |           |         |        |          |         |       |
+----------------+----------+------------+-----------+---------+--------+----------+---------+-------+
usuario1527166
fuente
aquí está la respuesta docs.angularjs.org/guide/providers#conclusion
JFouad

Respuestas:

391

En lugar de eso, simplemente llene la tabla con "sí" y "no" sin explicación, voy a entrar en un poco más de detalle.

[Nota, agregada 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: comprensión de la inyección de dependencia ]


El proveedor ( $provide)

El $provideservicio es responsable de decirle a Angular cómo crear nuevas cosas inyectables; Estas cosas se llaman servicios . Los servicios se definen por cosas llamadas proveedores , que es lo que estás creando cuando las usas $provide. La definición de un proveedor se realiza a través del providermétodo en el $provideservicio, y puede obtener el $provideservicio solicitando que se inyecte en la configfunción de 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 nombrada greetingen cualquier función inyectable (como controladores, más sobre eso más adelante) y Angular llamará a la $getfunción del proveedor para devolver una nueva instancia del servicio. En este caso, lo que se inyectará es una función que toma un nameparámetro y alertun 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,, servicey valueson 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 exactamente ese mismo proveedor así:

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

Es importante entenderlo, así que lo reformularé: bajo el capó, AngularJS está llamando exactamente el mismo código que escribimos anteriormente (la $provide.providerversión) para nosotros. Literalmente, 100% no hay diferencia en las dos versiones. valuefunciona de la misma manera: si lo que devolviéramos de nuestra $getfunción (también conocida como nuestra factoryfunción) es siempre exactamente igual, podemos escribir aún menos código usando value. Por ejemplo, dado que siempre devolvemos la misma función para nuestro greetingservicio, también podemos usarla valuepara definirla:

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

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

Ahora probablemente notaste esta app.config(function($provide) { ... })cosa molesta que he estado usando. Dado que la definición de nuevos proveedores (a través de cualquiera de los métodos anteriores) es muy común, AngularJS expone los $providermétodos 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 estos hacen lo mismo que las app.config(...)versiones más detalladas que utilizamos anteriormente.

El inyectable que me he saltado hasta ahora es constant. Por ahora, es bastante fácil decir que funciona igual value. Veremos que hay una diferencia más tarde.

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 $provide(sin juego de palabras). Cada vez que escribe una función que toma argumentos inyectados, está viendo el inyector en funcionamiento. Cada aplicación AngularJS tiene un único $injectorque se crea cuando se inicia por primera vez; puede obtenerlo inyectándose $injectoren cualquier función inyectable (sí, ¡ $injectorsabe cómo inyectarse!)

Una vez que lo haya hecho $injector, puede obtener una instancia de un servicio definido al llamarlo getcon 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 servicios mágicamente en cualquier función que tenga usando el invokemétodo 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 almacena en caché lo que el proveedor devuelve 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$injector.invoke . Esto incluye

  • funciones de definición del controlador
  • funciones de definición de directivas
  • funciones de definición de filtro
  • Los $getmétodos de los proveedores (también conocidos como las factoryfunciones de definición)

Como constantsys valuesiempre devuelven un valor estático, no se invocan a través del inyector y, por lo tanto, no puede inyectarles nada.

Configurar proveedores

Usted puede preguntarse por qué alguien se molestaría en configurar un proveedor de pleno derecho con el providemétodo si factory, value, etc, son mucho más fácil. La respuesta es que los proveedores permiten mucha 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 inyectarse en configsecciones de su aplicación para que pueda interactuar con ellos!

Primero, Angular ejecuta su aplicación en dos fases: las fases configy run. La configfase, como hemos visto, es donde puede configurar cualquier proveedor según sea necesario. Aquí también se configuran las directivas, los controladores, los filtros y similares. La runfase, como puede suponer, es donde Angular realmente compila su DOM e inicia su aplicación.

Puede agregar código adicional para que se ejecute en estas fases con las funciones myMod.configy myMod.run, cada una 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 incorporado $provideen nuestro primer ejemplo de código. Sin embargo, lo que vale la pena destacar es que durante la configfase, solamente los proveedores pueden ser inyectadas (con la excepción de los servicios en el AUTOmodule-- $providey $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 tiene acceso es a cualquier proveedor de servicios que haya realizado:

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

Hay una excepción importante: los constants, dado que no se pueden cambiar, pueden inyectarse dentro de los configbloques (así es como difieren de los values). Se accede a ellos solo por su nombre (no es Providernecesario el sufijo).

Cada vez que define un proveedor para un servicio, ese proveedor recibe un nombre serviceProvider, donde serviceestá 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 setTextque podemos usar para personalizar nuestro alert; Podemos acceder a este proveedor en un configbloque para llamar a este método y personalizar el servicio. Cuando finalmente ejecutamos nuestra aplicación, podemos obtener el greetingservicio y probarlo para ver que nuestra personalización entró en vigencia.

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 inyectarse, pero los controladores mismos no pueden inyectarse en otras cosas. Esto se debe a que los controladores no se crean a través del proveedor. En cambio, hay un servicio angular incorporado llamado $controllerque es responsable de configurar sus controladores. Cuando llama myMod.controller(...), en realidad está accediendo al proveedor de este servicio , al igual que en la última sección.

Por ejemplo, cuando define un controlador como este:

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

Lo que realmente 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 $controllerservicio (que a su vez usa el $injectorpara invocar la función de su controlador para que también inyecte sus dependencias).

Filtros y Directivas

filtery directivefunciona exactamente de la misma manera que controller; filterusa un servicio llamado $filtery su proveedor $filterProvider, mientras que directiveusa un servicio llamado $compiley su proveedor $compileProvider. Algunos enlaces:

Según los otros ejemplos, myMod.filtery myMod.directiveson atajos para configurar estos servicios.


tl; dr

Entonces, para resumir, cualquier función con la que se llame $injector.invoke puede inyectarse . Esto incluye, de su gráfico (pero no se limita a):

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

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

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

Dicho esto, a los servicios integrados les gusta $controllery $filter pueden inyectarse, y puede usar esos servicios para obtener los nuevos filtros y controladores que definió con esos métodos (a pesar de que las cosas que definió no son, por sí mismas, capaces de ser inyectado en las cosas).

Aparte de eso, cualquier función inyector-invocado puede ser inyectado con cualquier servicio de proveedor-proporcionado - no hay ninguna restricción (aparte de la configy rundiferencias aparecen en este documento).

Michelle Tilley
fuente
66
¡Guauu! ¡Gracias por tomarse el tiempo para responder con tanto detalle! Lo he leído dos veces y creo que lo he entendido bastante. Voy a estudiarlo y los enlaces que diste en detalle más tarde hoy. Y otro +1 para el gato. :)
user1527166
18
Una de las respuestas SO más útiles y detalladas que he encontrado, ¡gracias!
Godders
11
Esta respuesta define un nuevo nivel de asombroso. Cosas iluminadoras
Ngure Nyaga
44
Con mucho, el mejor recurso que he encontrado para AngularJS. Gracias.
código90
55
Literalmente, la mejor pieza de documentación de AngularJS que he visto. ¡Camino a seguir!
Iain Duncan el
13

El punto que BinaryMuse hace en su increíble respuesta acerca de que los proveedores, las fábricas y los servicios son lo mismo extremadamente importante.

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

AngularJS son solo proveedores
(fuente: simplygoodcode.com )

Luis perez
fuente
7

Gran respuesta de Michelle. Solo quiero señalar que se pueden inyectar directivas . Si tiene una directiva llamada myThing, puede inyectarla 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 .

Gil Birman
fuente
Parece que el segundo ejemplo para decorar esa directiva no funciona desde Angular 1.4. (ver comentario de Juan Biscaia allí)
Vadorequest