Espera, ¿qué? Otro artículo más que responde a la gran pregunta: Service vs Factory, ¿qué debo usar? Sí, parece que esto ya no es necesario, ya que hay una tonelada de recursos en internet que discuten ese tema. Resulta que esta pregunta sigue apareciendo cada semana más o menos en diferentes canales, e incluso después de leer las diez primeras respuestas en StackOverflow, sigue sin estar muy claro. A pesar de eso, también parece que los recursos actuales en la web no promueven realmente la mejor práctica, especialmente si consideramos los recientes movimientos de la plataforma web. Este artículo explica de una vez por todas la diferencia entre servicios y fábricas y por qué queremos preferir los servicios a las fábricas.
La diferencia entre servicios y fábricas
Bueno, entonces ¿cuál es la diferencia entre un servicio y una fábrica en AngularJS? Como todos sabemos, podemos definir un servicio así:
app.service('MyService', function () { this.sayHello = function () { console.log('hello'); };});
.service()
es un método en nuestro módulo que toma un nombre y una función que define el servicio. Bastante sencillo. Una vez definido, podemos inyectar y utilizar ese servicio concreto en otros componentes, como controladores, directivas y filtros, así:
app.controller('AppController', function (MyService) { MyService.sayHello(); // logs 'hello'});
Okay, claro. Ahora lo mismo que una fábrica:
app.factory('MyService', function () { return { sayHello: function () { console.log('hello'); } }});
De nuevo, .factory()
es un método en nuestro módulo y también toma un nombre y una función, que define la fábrica. Podemos inyectar y utilizar esa cosa exactamente de la misma manera que hicimos con el servicio. Ahora, ¿cuál es la diferencia aquí?
Bueno, puedes ver que en lugar de trabajar con this
en la fábrica, estamos devolviendo un objeto literal. ¿Por qué es eso? Resulta que un servicio es una función constructora mientras que una fábrica no lo es. En algún lugar profundo de este mundo Angular, hay este código que llama a Object.create()
con la función constructora del servicio, cuando se instancian. Sin embargo, una función de fábrica es realmente una función que se llama, por lo que tenemos que devolver un objeto explícitamente.
Para que esto quede un poco más claro, podemos simplemente echar un vistazo al código fuente de Angular. Este es el aspecto de la función factory()
:
function factory(name, factoryFn, enforce) { return provider(name, { $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn });}
Toma el nombre y la función de fábrica que se le pasa y básicamente devuelve un proveedor con el mismo nombre, que tiene un método $get
que es nuestra función de fábrica. Entonces, ¿qué pasa con esto del proveedor? Bueno, cada vez que le pides al inyector una dependencia específica, básicamente le pide al proveedor correspondiente una instancia de ese servicio, llamando al método $get()
. Por eso se requiere $get()
, cuando se crean proveedores.
En otras palabras, si inyectamos MyService
en algún sitio, lo que ocurre entre bastidores es:
MyServiceProvider.$get(); // return the instance of the service
De acuerdo, las funciones de fábrica sólo se llaman, ¿qué pasa con el código del servicio? Aquí hay otro fragmento:
function service(name, constructor) { return factory(name, );}
Oh, mira, resulta que cuando llamamos a service()
en realidad llama a factory()
. Pero no sólo pasa nuestra función constructora del servicio a la fábrica tal cual. Pasa una función que pide al inyector que instancie y objeto por el constructor dado. En otras palabras: un servicio llama a una fábrica predefinida, que termina como método $get()
en el proveedor correspondiente. $injector.instantiate()
es el método que finalmente llama a Object.create()
con la función del constructor. Por eso usamos this
en los servicios.
De acuerdo, así que resulta que, no importa lo que usemos, service()
o factory()
, siempre se llama a una fábrica que crea un proveedor para nuestro servicio. Lo que nos lleva a la pregunta más formulada en la historia de Angular:
¿Cuál usar?
Hacer esa pregunta en internet nos lleva a un par de artículos y respuestas de StackOverflow. La primera es esta respuesta. Dice:
«Básicamente la diferencia entre el servicio y la fábrica es la siguiente:»
app.service('myService', function() { // service is just a constructor function // that will be called with 'new' this.sayHello = function(name) { return "Hi " + name + "!"; };});app.factory('myFactory', function() { // factory returns an object // you can run some code before return { sayHello : function(name) { return "Hi " + name + "!"; } }});
Ahora ya sabemos lo que ocurre entre bastidores, pero esta respuesta añade otro comentario. Dice que podemos ejecutar código antes de devolver nuestro literal de objeto. Eso básicamente nos permite hacer algunas cosas de configuración o crear condicionalmente un objeto o no, lo que no parece ser posible al crear un servicio directamente, por lo que la mayoría de los recursos recomiendan usar fábricas en lugar de servicios, pero el razonamiento es inapreciable.
¿Y si te digo que podemos hacer exactamente lo mismo con los servicios también?
Sí, correcto. Un servicio es una función constructora, sin embargo, eso no nos impide hacer un trabajo adicional y devolver literales de objetos. De hecho, las funciones constructoras en JavaScript pueden devolver lo que quieran. Así que podemos tomar nuestro código de servicio y escribirlo de forma que básicamente haga exactamente lo mismo que nuestra fábrica:
app.service('MyService', function () { // we could do additional work here too return { sayHello: function () { console.log('hello'); }; }});
Hoppla, ¿y ahora qué? Nos acabamos de dar cuenta de que, dependiendo de cómo escribamos nuestros servicios, ya no hay ninguna diferencia entre ambos. La gran pregunta sigue siendo: ¿Cuál debemos usar?
Los servicios nos permiten usar clases ES6
Por supuesto, escribir los servicios de esa manera es algo contra productivo, ya que se llama como una función constructora, por lo que también debe usarse como tal. ¿Hay alguna ventaja sobre lo otro entonces? Sí, la hay. Resulta que, en realidad, es mejor utilizar servicios siempre que sea posible, cuando se trata de migrar a ES6. La razón es simplemente que un servicio es una función constructora y una fábrica no lo es. Trabajar con funciones constructoras en ES5 nos permite utilizar fácilmente las clases de ES6 cuando migramos a ES6.
Por ejemplo, podemos tomar nuestro código y reescribirlo en ES6 así:
class MyService { sayHello() { console.log('hello'); }}app.service('MyService', MyService);
Una clase de ES6 es realmente sólo una función constructora en ES5. Escribimos sobre esto en Usando ES6 con Angular hoy, si no has leído ese artículo todavía, te recomendaría revisarlo.
Con las fábricas, esto no es posible porque simplemente se llaman como funciones. Espero que este artículo haya dejado todo claro y anime a la gente a no usar fábricas en lugar de servicios, si no saben qué usar.
¡Esto y más se aprende en nuestra Master Class de Angular!