Una pregunta que se hace mucha gente al empezar con AngularJS es acerca de los servicios: ¿qué diferencia hay entre factory y service? ¿y entre value y service? ¿cómo sé cual tengo que utilizar a cada momento?
Hoy voy a intentar explicar las diferencias entre cada uno de estos métodos que nos proporcionan los módulos de AngularJS para crear servicios (los llamaremos así de forma genérica).
¿Qué son los servicios?
Lo primero que necesitas saber es que en AngularJS los servicios son objetos singleton, inyectables por Dependency Injection, donde definimos la lógica de negocio de la aplicación, con el objetivo de que sea reutilizable e independiente de las vistas.
¿Cómo creo servicios?
Los módulos de Angular exponen 5 métodos para definir servicios, que son:
- constant
- value
- service
- factory
- provider
Guía rápida
A modo resumen, podemos decír que constant y value solo sirven para objetos simples que no necesitan de inyectar otros elementos, y que además constant (que debería guardar solo constantes) se puede utilizar para configurar la aplicación.
Por otro lado, tendríamos provider, factory y service, que nos permiten crear objetos mucho más complejos que dependen de otros objetos (por inyección de dependencias). Cada uno es un caso más concreto del anterior, y como gran elemento diferencial, provider permite generar una API para configurar el servicio resultante.
A nivel de detalle
Si lo que queremos es entender al detalle las diferencias entre cada uno de estos elementos, antes de seguir, es importante explicar el ciclo de vida AngularJS.
Ciclo de vida
El ciclo de vida de una aplicación en AngularJS se divide en dos fases: la de configuración y la de ejecución.
Fase de configuración
La fase de configuración se ejecuta en primer lugar. Durante esta fase los servicios aún no pueden instanciarse (no pueden pasarse servicios por DI). El objetivo de esta fase es definir cual va a ser la configuración de nuestra aplicación a la hora de ejecutarse. Cuidado con querer configurar un servicio en la resolución de un método asíncrono en esta fase, por que el ciclo de configuración podría haber finalizado antes.
En esta fase, solo podremos inyectar constants y providers.
Fase de ejecución
La fase de ejecución es donde se ejecuta toda la lógica de la aplicación y empieza una vez ha concluido la fase de configuración. La interacción entre nuestras vistas, controladores y servicios, sucede en esta fase.
En esta fase podemos inyectar constants, values, services y factories.
Constant
Constant sirve para almacenar valores simples de cualquier tipo que no deben cambiar, NO podemos inyectar dependencias (DI) en su definición, y tampoco es configurable, pero SI puede inyectarse en funciones de configuración.
Un ejemplo de definición de constante sería el siguiente:
myApp.constant('SERVERS',{ DEVELOPMENT: "http://localhost:8080/app", PRODUCTION:"http://myDomain.com/app"});
Y se utilizaría (tanto en config, como en run, controller, service, etc. ) del siguiente modo:
myApp.config(['SERVERS', function(SERVERS){
console.log(SERVERS.PRODUCTION);
}]);
Value
Value nos permite definir objetos simples y primitivas que se pueden inyectar únicamente durante la fase de ejecución. NO podemos inyectar dependencias (DI) en su definición ni es configurable.
Algunos ejemplos serían los siguientes:
myApp.value('randomize',function(){
return Math.floor(Math.random()*10000);
})
myApp.value('token','a1234567890');
myApp.value('User',{'id': 'someId'})
Se utilizarían en la fase de ejecución (run, controller, service, etc. ) del siguiente modo:
myApp.run(['randomize', 'User', function(randomize, User){
var num = randomize();
User.id = num;
}]);
Service
Un servicio es una función constructor que define el servicio. Este servicio se puede inyectar únicamente durante la fase de ejecución. No obstante, SI podemos inyectar dependencias (DI) en su definición, aunque no es configurable.
Internamente, Angular utiliza el método new sobre este constructor a la hora de instanciar el servicio, por lo que podemos añadirle propiedades con this. Ese objeto this es exactamente lo que nos devuelve el servicio.
Veamos un ejemplo de definición de servicio, donde inyectamos una dependencia (el value token del punto anterior):
myApp.service('AuthBearer', ['token', function(token) {
this.authValue = "bearer " + token;
}]);
Y se utilizaría en fase de ejecución (run, controller, service, etc. ) del siguiente modo:
myApp.run(['AuthBearer', function(AuthBearer){
console.log(AuthBearer.authValue);
}]);
Factory
Una factoría es un caso más genérico de service, más enfocado a la inicialización del servicio dado que no devuelve el constructor sino el objeto en sí mismo. Como en el servicio, se puede inyectar únicamente durante la fase de ejecución, y SI podemos inyectar dependencias (DI) en su definición, aunque no es configurable.
Un ejemplo de definición sería el siguiente:
myApp.factory('apiToken', ['$window', 'clientId', function apiTokenFactory($window, clientId) {
var encrypt = function(data1, data2) {
// NSA-proof encryption algorithm:
return (data1 + ':' + data2).toUpperCase();
};
var secret = $window.localStorage.getItem('myApp.secret');
var apiToken = encrypt(clientId, secret);
return apiToken;
}]);
Y lo inyectaríamos como un servicio:
myApp.run(['apiToken', function(apiToken){
console.log(apiToken);
}])
Provider
El provider es el caso más genérico de servicio, que además de generar un servicio inyectable durante la fase de ejecución e inyectar dependencias (DI) en su definición, proporciona una API para la configuración del servicio antes de que se inicie la aplicación.
Un provider se definiría de la siguiente forma:
myApp.provider('logger', function(){
var logToConsole = false;
this.enableConsole = function(flag){
logToConsole = flag;
};
this.$get = function(){
return {
debug: function(msg){ if(logToConsole){ console.log(msg);} }
};
};
})
Donde los métodos de this conforman la API de configuración, y el método this.$get equivale a una factoría.
Para configurar el servicio logger, tendríamos que usar su API en la fase de configuración, inyectando el loggerProvider:
myApp.config(['loggerProvider', function(loggerProvider){
loggerProvider.enableConsole(true);
}])
Luego en la fase de ejecución, utilizaríamos el servicio logger del modo habitual:
myApp.run(['logger', function(logger){
logger.debug('Hello world');
}])
Relación provider/factory/service
Para concluir, podríamos decir que la relación entre los distintos métodos que tenemos para crear servicios complejos, es la que ilustra el siguiente esquema:
Espero que te haya clarificado estos conceptos. Si te ha gustado, ¡compártelo!
Hola, quisiera saber ¿qué debo tener en cuenta para saber cual servicio utilizar? segun sé el más util es el provider y el mas usado es el factory, pero para contruir mi aplicación no sé que es mejor tener como provider, service o factory.
¡Hola!
El provider, te permite configurar el servicio antes de instanciarlo, así que esa es una pista muy buena de cuando utilizarlo.
Siempre que necesites hacer algo en la fase de configuración -> PROVIDER.
El factory es algo más flexible que el service, pero en general los puedes utilizar para lo mismo. Yo prefiero el factory, pero vamos, eso va a gustos. Si quieres ser purista, te diría que el factory es la mejor elección para instanciar nuevos objetos desde el servicio singleton, mientras que el service está más orientado a simplemente tener un servicio singleton.
¡Saludos!
Se pueden crear varios controladores con con el mismo provider y diferentes configuraciones, si es asi podrias crear un ejemplo.
Muchas Gracias
Buen articulo, me sacaste de dudas. Gracias por tus aportes.