Servicios de AngularJS
@gruizdevilla
@felixzapata
@adesis
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í:
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.
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:
- Lo pides ( y AngularJS te lo da)
- 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);
};
});
'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
-
Le damos un nombre al servicio
- 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:
function factory(name, factoryFn) {
return provider(name, { $get: factoryFn });
}
.service es parecido.
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?
Servicios de AngularJS
By Gonzalo Ruiz de Villa
Servicios de AngularJS
Introducción a los distintos tipos de servicios de AngularJS
- 49,183