Inyección de dependencias en Angular

Hace unos días mi compañero XaviPaper y yo nos plantemos crear un repositorio en github MagicCrudAngular y si te centras exclusivamente en el nombre quizá no veas más que una solución para hacer Crud.

Bueno, pues ya te digo yo que no. Se puede resolver cualquier llamada a servicios REST de forma declarativa(es decir sin escribir ni una sola línea de código JavaScript).

Quizá a estás alturas te puedes preguntar y que tiene que ver esto con DI(Inyección de dependencias), pues sencillo gracias a la potencia de Angularjs en este campo, puedes hacer maravillas como esta.

Para ello os voy a explicar como funciona la DI en Angularjs.

1. Declaras un modulo con la siguiente instrucción.

angular.module(‘myModule’,[],config);

El primer parámetro es el nombre de tu módulo(requerido).

El segundo parámetro es una array de string con los módulos de los que depende tu módulo(requerido empty sino tienes dependencias).

El tercer parametro es una función de configuración.

2. Una vez tengas declarado tu módulo puedes acceder al módulo llamando a la misma función con el nombre de tu módulo.

angular.module(‘myModule’)

Es justo en este momento cuando empiezas a trabajar con el contenedor de dependencias registrando dependencias a través de los métodos.

-directive.

-service.

-factory.

-provider.

-value.

-constant.

-filter

-config

-run

Excepto value y constant que no tienen otro objetivo que almacenar valores y constantes el resto de métodos tienen la siguiente firma.

Un ejemplo de un factory.

angular.module(‘myModule’).factory(‘myFactory’,function($http){});

El primer parámetro es el nombre de tu factoría y el siguiente una función a la que se le va a injectar como parametro el provider $http.

Las formas de declarar las dependencias de una función se pueden hacer de tres formas.

1. De forma implícita. Es decir asumes que el nombre del parámetro es un objeto registrado en el contenedor. Esto tiene un gran problema y es a la hora de minificar tu código. Con lo cual solo lo tienes que utilizar para pruebas, o teniendo claro que nunca vas a minificar. Esta forma es desaconsejable.

angular.module(‘myModule’).factory(‘myFactory’,function($http){});

2. $inject property annotation. Declaras una función de javaScript y después una propiedad estática llamada $inject, que es una array de string con el nombre de los objetos que vas a inyectar a tu función.

   1: var myFactory = function(http){

   2: }

   3: myFactory.$inject=[‘$http’]

   4: angular.module(‘myModule’).factory(‘myFactory’,myFactory);

Observa que en $inject le decimos en este caso que queremos inyectar en la función myFactory el provider $http en el primer parametro, en este caso solo tiene uno. y después al parámetro le podemos llamar como queramos, en mi caso http. Concretamente es el que normalmente utilizo yo.

Te recomiendo esta lectura

Function Declarations vs. Function Expressions

El motivo no es otro que tu en JavaScript puedes hacer lo mismo a través de una función autoejecutable.

   1: (function(module){

   2: myFactory.$inject=[‘$http’]; 

   3: function myFactory(http){

   4: }

   5: module.factory(‘myFactory’,myFactory);

   6: })(angular.module(‘myModule’);

3.Inline array annotation. se suele utilizar en provider y directives por aquello que puedes tener varias funciones inyectables.

La forma para nuestro ejemplo sería esta.

   1: angular.module('myModule',['$http',function(http){}]);

Esta al igual que la anterior resuelve el problema de los minificadores, pero yo entre otras cosas la veo engorrosa con lo cual la intento evitar en la medida de lo posible.

El service locator.

En nuestro caso y dada la versatilidad de nuestro componente, nos encontramos que al analizar todo nuestras funciones podían tener un numero muy grande de parámetros y la verdad que nos gustaba poco, con lo cual lo que hicimos fue resolver muchas dependencias por medio de service locator.

La pregunta es como puedo acceder a service locator en angular.

Pues hay varias formas.

1. Inyectar como parámetro a una función $inject.

2. Utilizar el método global angular.injector de la siguiente forma.

angular.injector([‘ng’]);

Recibe un paremetro que es una array de los módulos que tienes.

3. Obtenerlo a través de angular.element.

angular.element(document.body).injector();

El parámetro es un elemento html.

4. Por medio de data en un angular.element.

angular.element(document.body).data(‘$injector’);

De todas en producción solo debes de utilizar la primera, el resto las puedes utilizar para depuración.

Una vez que obtienes el injector te devuelve un objeto con la siguiente firma

image

-has. Devuelve true o false al invocarlo y como parametro se le pasa el nombre del servicio.

injector.has(‘$http’)

Devuelve true.

-get. devuelve la instancia del objeto.

injector.get(‘$http’);

Devulve el provider $http

Este método lanza una excepción si el servicio no se encuentra registrado en el contenedor de dependencias.

annotate. Se le pasa una función y nos devuelve un array de los parametros que tiene. Si tiene $inject devuelve este y sino pasa una expresión regular y construye el $inject.

angular.injector([‘ng’]).annotate(function(x,y){});

En este caso devuelve un array de string de la siguiente forma.

[‘x’,’y’]

Con lo cual he aquí el misterio de la inyección de dependencias en angular, analizo los parámetros de una función , llamo a get y guardo en cache para devolverlo la siguiente vez.

-invoke. Nos permite llamar a una función e inyectarle los parametros necesarios. Con este método se llama a una función por invocación.

angular.injector([‘ng’]).invoke(function($http){console.log(this)});

Puedes ver que el resultado y this es el objeto global window.

Utilizado para llamar a factorias en Angular.

Esto es equivalente a ejecutar en JavaScipt;

var x = (function(){console.log(this)})()

-instantiate. Hace lo mismo que invoke pero la llamada la hace a través de un constructor.

angular.injector([‘ng’]).instantiate(function($http){console.log(this)});

Observa que en este caso this no es window, sino la instancia del objeto.  Se utiliza para servicios,providers y controllers

Esto es equivalente a hacer en JavaScript.

var x = new function(){console.log(this)}.

Es justo en este momento cuando vas a ser capaz de entender porque todos los servicios,factorias,provider y directivas son Singleton. 

La respuesta es sencilla, angular a través de injector y una vez que llamas al método get. almacena en cache la instancia del objeto.

   1: function getService(serviceName) {

   2:       if (cache.hasOwnProperty(serviceName)) {

   3:         if (cache[serviceName] === INSTANTIATING) {

   4:           throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- '));

   5:         }

   6:         return cache[serviceName];

   7:       } else {

   8:         try {

   9:           path.unshift(serviceName);

  10:           cache[serviceName] = INSTANTIATING;

  11:           return cache[serviceName] = factory(serviceName);

  12:         } catch (err) {

  13:           if (cache[serviceName] === INSTANTIATING) {

  14:             delete cache[serviceName];

  15:           }

  16:           throw err;

  17:         } finally {

  18:           path.shift();

  19:         }

  20:       }

  21:     }

Que objetos son por instancia, exclusivamente los controlladores y los scopes.

Hay gente que se pregunta el porque. Para mi es sencillo, ahorro de memoria, performance, etc. Y aunque en un principio, puede llegar a ser traumático una vez que te acostumbras puedes vivir perfectamente con singleton en factorias,servicios y providers.

Hay una forma de resolver esto y es crear una factoria que devuelva una nueva instancia de un objeto, pero esto es algo que se puede hacer en el propio JavaScript y es muy utilizado en Node.js.

Os pongo un ejemplo.

   1: module.factory('myFactory', function ($http) {

   2:  

   3:         var myInstance = function () {

   4:  

   5:         }

   6:         myInstance.prototype.show = function () {

   7:  

   8:         };

   9:         ....

  10:         return{

  11:             getInstance:function(){

  12:                 return new myInstance();

  13:             }

  14:         }

  15:     });

 

Como puedes observar retorno una función getInstance que al ser invocada devuelve una nueva instancia del objeto myInstance. Con lo cual solucionado el problema de singleton en factorias,servicios y providers aunque no es aconsejable ,excepto que realmente lo necesites.

Como ya hemos explicado anteriormente utilizamos service locator para poder hacer dinámica la declaración de dependencias. Os pongo un ejemplo de la directiva mg-ajax para el comportamiento de un index.

<mg-ajax data-path=’invoices’ data-options=’mgIndex/>

Que va a hacer esto en nuestro html.

1. Llamar a nuestro controlador invoices y simular el comportamiento de un index.

2. Crear un array de invoices en la cual podemos iterar perfectamente para dibujar nuestras facturas

Para ello tenemos un comportamiento predefinido de esta forma para el atributo data-options.

module.factory(‘mgIndex’, function () {

       return {

           as: ‘index’,

           init: ‘index.filter={page:0,records:20}’,

           isArray:true,

           method: ‘query’,

           service: ‘mgHttp’,

           cacheService: ‘mgCacheFactory’,

           cache: ‘["filter"]’,

           before: ‘mgBeforeHttpFactory’,

           success: ‘mgSuccessFactoryIndex’,

           error: ‘mgErrorHttpFactory’,

           cmd: ‘mgCommandIndex’,

           auto: ‘accept’

       };

   });

Os paso un link al código fuente donde podéis ver más claro el uso de service locator.

Utilización de service locator

Como podéis observar iteramos por una array de string para resolver las dependendencias de nuestro controlador en la directiva mg-ajax.

Referencias.

Dependency Injection en Angular

Service locator en angular

$injector

Conclusiones

En este y en otra serie de post voy a explicar cada una de las cosas que se pueden hacer con el repositorio mgcrud y te vas a dar cuenta como se pueden resolver casi el 98% de los casos de invocación a servicios RESTfull de forma declarativa.

En el siguiente post explicare a fondo el funcionamiento de

-$parse

-scope.$eval.

-$interpolate

Para de esa forma cerrar el ciclo de la declaración dinámica a través de html en angularjs y posteriormente pasar a explicar cada una de las diferentes opciones que ofrece mgcrud.

 

 

 

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *