Servicios de AngularJS


@gruizdevilla
@felixzapata
@adesis

Meetup AngularJS Madrid

Antes de comenzar la cuña habitual:



Si quieres trabajar con AngularJS (en Madrid y México DF) habla con nosotros:

@gruizdevilla
@felixzapata
@adesis
rrhh@adesis.com

El segundo tópico del día:




Disculpas



Para los que dominan AngularJS

Y esto no les aporta nada


Para los que los que saben poco de AngularJS

Y no se enteran y no les aporta nada.


Para los que saben mucho EcmaScript

Y quieren ver algo de harmony por aquí: yield y generators, arrow functions, let let let, desestructuraciones, default y rest parameters, spread operators, etc..


Para los que vienen de otros lenguajes

Y se pierden con la sintaxis de JavaScript, con lo bonito que es Lisp/Clojure/Racket :p


Para los que saben de promesas

Y piensan otra vez un pesado hablando de las promesas, a ver cuando pasan a los observables...


Para los que ponen cara rara cuando les hablan de promesas


y se las prometían muy felices en esta conferencia



Mejor paro con el anuncio de Coca Cola....

En cualquier caso:



Enjoy!*




(* Eslogan de Coca Cola en 1999 en USA, Reino Unido y Canada)

Sobre la indumestaria: voy con traje pero también pico código :)

La prueba:

(esta camiseta significa que me han aceptado PRs en el proyecto de AngularJS)

Los ejemplos de esta presentación los puedes encontrar aquí:



https://github.com/gonzaloruizdevilla/angularjsmadridservicios

Las piezas fundamentales de AngularJS:


  • Controladores
  • Servicios
  • Filtros
  • Directivas

 

¿Qué son los servicios?



  • Son singletons, solo hay uno de cada uno.
  • O sea, que se instancia una vez y se comparte.
  • UNO

Es que a veces se olvida esto....


¿Qué son los servicios?



  • Sirven para realizar tareas comunes a la aplicación, como $http para peticiones Ajax

¿Qué son los servicios?


  • Es lo que nos permite simplificar los controladores, extrayendo detalles específicos de negocio.
    Separar responsabilidades.

¿Qué son los servicios?


  • Permiten la comunicación entre controladores sin que estos queden acoplados


* Los controladores se pueden comunicar entre si con más técnicas, como scopes heredados y $rootScope.$broadcast

¿Qué son los servicios?

Los de AngularJS empiezan por "$", como todo lo demás de AngularJS.

Algunos de los más usados:
  • $http
  • $q
  • $timeout, $interval (MEMORY LEAK DANGER AHEAD)
  • $anchorScroll
  • $location
  • $window
  • $log

¿Qué son los servicios?



También pueden ser clientes de otros servicios (muchos de los que programarás)




¿Cómo se usa un servicio?


Probablemente sea lo más fácil de usar de AngularJS.
Dos pasos:

  1. Lo pides ( y AngularJS te lo da)
  2. Lo usas

Ejemplo tontaco



angular.module('angularJsmadridServiciosApp') .controller('MainCtrl', function ($scope, Calculadora) { $scope.total1 = Calculadora.suma(1,2); $scope.total2 = Calculadora.suma(1,2,3); $scope.total3 = Calculadora.suma(1,2,3,4); });

<p>Total: {{total1}}</p> <p>Total: {{total2}}</p> <p>Total: {{total3}}</p>

¿Cómo es el servicio?

El cuerpo es algo tan sencillo como:



'use strict';

angular.module('angularJsmadridServiciosApp')
  .service('Calculadora', function Calculadora() {
    this.suma = function () {
      //...
    };
  });


El servicio completo

porque hacer folding de listas es más divertido ;)




'use strict';

angular.module('angularJsmadridServiciosApp')
  .service('Calculadora', function Calculadora() {
    this.suma = function () {
      return [].reduce.call(arguments,
        function(acc, val){return acc + val;},
        0);
    };
  });

Offtopic...


Si alguna vez habéis confundido "apply" con "call", sin tener claro cual recibía los argumentos como argumentos o como lista (valga la redundancia)

  fun.apply(thisArg[, argsArray])
  fun.call(thisArg[, arg1[, arg2[, ...]]])

Regla nemotécnica:



Apply y array:

  • empiezan por A
  • acaban por Y
  • y tienen el mismo número de letras
  • y tienen nos consonantes repetidas (esto ya es un poco forzado...)



¿Cómo se crea un servicio?






(alguno estará pensando: "por fin...")

Valores y constantes



module("x").constant(nombre, valor)

module("x").value(nombre, valor)

Servicios puros



module("x").service(nombre, fn)

module("x").factory(nombre, fn)

module("x").provider(nombre, fn)

Decoradores de servicios



$provider.decorator(nombre, fn)

Registrando un servicio


  1. Le damos un nombre al servicio
  2. Proporcionamos una función que es una FACTORÍA


El objetivo de la factoría es crear una función o un objeto que representa al servicio y que será pasado a los elementos que lo pidan.

Nota sobre las factorías


Se utilizan de forma perezosa o lazy.




Hasta que no se pide el servicio por primera vez no se invoca a la factoría.

Esto es bueno (hasta que deja de serlo)

.constant(nombre, valor)


Para crear constantes reutilizables desde otros sitios de la aplicación. 
El valor puede ser un tipo básico o un objeto.

 angular.module('angularJsmadridServiciosApp')
  .constant('E', 2.718281828459045);


.value(nombre, constructor)

Para registrar piezas reutilizables desde otros sitios de la aplicación. 
El valor puede ser un tipo básico o un objeto.

 angular.module('angularJsmadridServiciosApp')
  .value('c', 300000000);

Lo mismo que constant, pero con un matiz importante:



Al igual que los servicios, las constantes registradas con "value" pueden ser decoradas. 

Es decir, donde hay esta inyección

MiServicio -> MiControlador

Pasa a ser


MiDecorador(MiServicio) -> MiControlador

Luego volveremos sobre esto.

.service(nombre, constructor)


La función es un constructor.


AngularJS crea la factoría que hará un "new" de la función.

El objeto registrado suele ser, por tanto, un objeto "tradicional" o prototipado de JS.


 function Contador() {
    this._cuenta = 0;
  }

  Contador.prototype.total = function (){
    return this._cuenta;
  };

  Contador.prototype.click = function (){
    this._cuenta++;
  };

  angular.module('angularJsmadridServiciosApp')
    .service('Contador', Contador);

Esta aproximación es sencilla pero limita la programación funcional.

Veamos el controlador del ejemplo  "4 - Service":
'use strict';
angular.module('angularJsmadridServiciosApp')
  .controller('Ej4Ctrl', function ($scope, Contador) {
    $scope.total = function () {
      return Contador.total();
    };
    $scope.click = function () {
      Contador.click();
    };
  });
Las "function" no parecen aportar demasiado.


Probemos con algo más sencillo:


'use strict';
angular.module('angularJsmadridServiciosApp')
  .controller('Ej5Ctrl', function ($scope, Contador) {
    $scope.total = Contador.total;
    $scope.click = Contador.click;
  });


Pero esto falla por el  this de las funciones .

Es muy habitual pasar referencias a funciones.

JavaScript es un lenguaje funcional. Impuro, limitado pero funcional. Gracias a eso, es cómodo trabajar con promesas y hacer cosas como*:
 $scope.click = function (){
  Servicio1.metodoX()
  .then(Servicio2.metodoY)
  .then(Servicio3.metodoX)
  .then(function (datos){
    $scope.resultado = datos;
  })
}
* Lo mismo en controladores como en otros servicios o piezas de AngularJS

.factory(nombre, factoría)


Registramos la factoría. 

Cuando AngularJS necesita un servicio invoca a nuestra factoría. El valor devuelto será el servicio.

El valor devuelto por nuestra factoría puede ser:


  • un objeto (hashmap)
  • una función
  • un híbrido (objeto usable como o a través de propiedades)

Lo + importante: crea una API clara.

Volviendo al ejemplo del Contador

'use strict';

angular.module('angularJsmadridServiciosApp')
  .factory('Contador2', function () {
    var cuenta = 0;
    function total(){
      return cuenta;
    }
    function click(){
      cuenta++;
    }
    return {
      total: total,
      click: click
    };
  });

    
    

A veces conviene salirse de la expresión "factory":

(function (){
  var cuenta = 0;
  function total(){
    return cuenta;
  }
  function click(){
    cuenta++;
  }
  angular.module('angularJsmadridServiciosApp')
  .factory('Contador3', function () {
    return {
      total: total,
      click: click
    };
  });
}());

Sin embargo, esto tiene una limitación.

El servicio se crea tal cual, de forma reactiva, pero no hay forma elegante de configurar la factoría.



.provider(nombre, provider)


El más potente y el más complicado.


Internamente, AngularJS convierte los anteriores a un provider.

Nuestra función debe devolver un objeto con el método $get


angular.module('angularJsmadridServiciosApp')
  .provider('Unavocal', function () {
    
    function traduce(s) { //función del servicio
      return s && s.replace(/[aeiou]/ig, 'a');
    }
    this.$get = function () { //factoría
      return {
        traduce: traduce
      };
    };
  });

.factory funcionan por debajo de esta forma:

https://github.com/angular/angular.js/blob/master/src/auto/injector.js#L632
function factory(name, factoryFn) {
  return provider(name, { $get: factoryFn }); 
}

.service es parecido.

https://github.com/angular/angular.js/blob/master/src/auto/injector.js#L634

function service(name, constructor) {
    return factory(name, ['$injector', function($injector) {
      return $injector.instantiate(constructor);
    }]);
  }

"instantiate" hace el new inyectando dependencias. 

Identifica si constructor es un array de la forma 

["dep1", "dep2", function (dep1, dep2){...}] 

o directamente

function (dep1, dep2){...}

El provider puede tener métodos para configurarlo al arrancar la aplicación

 angular.module('angularJsmadridServiciosApp')
  .provider('Unavocal', function () {
    var dialecto = 'a';
    this.setDialecto = function (s){ //método para configurar provider
      dialecto = s;
    };
    function traduce(s) { 
      return s && s.replace(/[aeiou]/ig, dialecto);
    }
    this.$get = function () {
      return {
        traduce: traduce
      };
    };
  });

Problema habitual:

Configurar un provider que no has registrado TODAVÍA.


Esto NO funciona:
module.config(function(miServicioProvider) {  });
module.provider('miServicio', function() {  });
Esto SÍ funciona:
module.provider('miServicio', function() {  });
module.config(function(miServicioProvider) {  });

Algunas cosas no son tan "lazy" como esperaríamos

Otra forma de registrar providers es en la configuración del módulo.

'use strict';

angular.module('angularJsmadridServiciosApp')
  .config(function($provide) {
    //def de dialecto, setDialecto y traduce...
    $provide.provider('Unavocal3', {
      setDialecto: setDialecto,
      $get: function () {
        return {
          traduce: traduce
        };
      }
    });
  });

Aparatoso pero ayuda a introducir los decorators

.decorator


Es la forma de abordar los "cross-cutting concerns".

Permite adaptar el uso de los servicios sin que sea percibido de forma explícita por el servicio o el cliente.

Usos típicos:

  • cachear
  • depurar/trazar
  • throttle
  • adaptar entradas y salidas a servicios

De nuevo, la lógica del "decorator":

donde hay esta inyección

MiServicio -> MiControlador

Pasa a ser

MiDecorador(MiServicio) -> MiControlador

En el "decorator" indicamos el servicio a decorar, y lo recibimos por $delegate:


angular.module('angularJsmadridServiciosApp')
  .config(function ($provide) {
    $provide.decorator('c2', function ($delegate) {
      var furlongsPorMetro = 201.168;
      return $delegate / furlongsPorMetro;
    });
  });

Los decorators se pueden concatenar:


MiDecorador(MiDecorador(MiServicio) )-> MiControlador

Metros→Furlongs→Rod


angular.module('angularJsmadridServiciosApp')
  .config(function ($provide) {
    $provide.decorator('c3', function ($delegate) {
      var furlongsPorMetro = 201.168;
      return $delegate / furlongsPorMetro;
    });
  })
  .config(function ($provide) {
    $provide.decorator('c3', function ($delegate) {
      var rodsPorFurlong = 40;
      return $delegate * rodsPorFurlong;
    });
  });

Suele ser práctico modificar directamente el $delegate, antes de devolverlo. Por ejemplo, con $log:

angular.module('angularJsmadridServiciosApp')
  .config(function ($provide) {
    $provide.decorator('$log', function ($delegate) {
      var debugFn = $delegate.debug; 
      
      $delegate.debug = function() {
        var now  = (new Date()).toLocaleTimeString();
        arguments[0] = now + ' - ' + arguments[0];
        debugFn.apply(null, arguments);
      };
      
      return $delegate;
    });
  });

Resumen rápido


  • "constant" y "value" registran directamente el valor pasado como servicio. "value" puede ser decorado.
  • "service" registra una función que es la constructora del servicio (se invoca con new)
  • "factory" registra una función responsable de crear el objeto singleton que será el servicio
  • "provider" es el mecanismo más avanzado y que más flexibilidad proporciona
  • "decorator" permite decorar servicios y resolver cross-cutting concerns 



¿Preguntas?

Made with Slides.com