Aspetta, cosa? Ancora un altro articolo che risponde alla grande domanda: Service vs Factory, cosa dovrei usare? Sì, sembra che questo non sia più necessario, dato che ci sono una tonnellata di risorse in internet che discutono questo argomento. Si scopre che questa domanda spunta ancora ogni settimana o giù di lì su diversi canali, e anche dopo aver letto le prime dieci risposte su StackOverflow, non è ancora molto chiaro. Nonostante ciò, sembra anche che le risorse attuali sul web non promuovano davvero la best practice effettiva, soprattutto se consideriamo i recenti movimenti della piattaforma web. ES6 ti sto guardando!
Questo articolo spiega una volta per tutte la differenza tra servizi e fabbriche e perché vogliamo preferire i servizi alle fabbriche.
La differenza tra servizi e fabbriche
Ok, quindi qual è la differenza tra un servizio e una fabbrica in AngularJS? Come tutti sappiamo, possiamo definire un servizio come questo:
app.service('MyService', function () { this.sayHello = function () { console.log('hello'); };});
.service()
è un metodo sul nostro modulo che prende un nome e una funzione che definisce il servizio. Abbastanza semplice. Una volta definito, possiamo iniettare e usare quel particolare servizio in altri componenti, come controller, direttive e filtri, come questo:
app.controller('AppController', function (MyService) { MyService.sayHello(); // logs 'hello'});
Ok, chiaro. Ora la stessa cosa di una factory:
app.factory('MyService', function () { return { sayHello: function () { console.log('hello'); } }});
Ancora una volta, .factory()
è un metodo sul nostro modulo e prende anche un nome e una funzione, che definisce la factory. Possiamo iniettare e usare questa cosa esattamente come abbiamo fatto con il servizio. Ora qual è la differenza qui?
Beh, potreste vedere che invece di lavorare con this
nella fabbrica, stiamo restituendo un oggetto letterale. Perché questo? Si scopre che un servizio è una funzione costruttrice, mentre una fabbrica non lo è. Da qualche parte nel profondo di questo mondo Angular, c’è questo codice che chiama Object.create()
con la funzione costruttore del servizio, quando viene istanziato. Tuttavia, una funzione factory è davvero solo una funzione che viene chiamata, ed è per questo che dobbiamo restituire un oggetto esplicitamente.
Per rendere questo un po’ più chiaro, possiamo semplicemente dare un’occhiata al codice sorgente Angular. Ecco come appare la funzione factory()
:
function factory(name, factoryFn, enforce) { return provider(name, { $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn });}
Prende il nome e la funzione factory che viene passata e sostanzialmente restituisce un provider con lo stesso nome, che ha un metodo $get
che è la nostra funzione factory. Quindi cos’è questa cosa del provider? Bene, ogni volta che chiedete all’iniettore una dipendenza specifica, fondamentalmente chiede al provider corrispondente un’istanza di quel servizio, chiamando il metodo $get()
. Questo è il motivo per cui $get()
è richiesto, quando si creano i provider.
In altre parole, se iniettiamo MyService
da qualche parte, quello che succede dietro le quinte è:
MyServiceProvider.$get(); // return the instance of the service
Va bene, le funzioni di fabbrica vengono semplicemente chiamate, e il codice del servizio? Ecco un altro frammento:
function service(name, constructor) { return factory(name, );}
Oh guarda, si scopre che quando chiamiamo service()
in realtà chiama factory()
. Ma non passa semplicemente la nostra funzione costruttrice del servizio alla fabbrica così com’è. Passa una funzione che chiede all’iniettore di istanziare un oggetto con il costruttore dato. In altre parole: un servizio chiama una fabbrica predefinita, che finisce come metodo $get()
sul fornitore corrispondente. $injector.instantiate()
è il metodo che alla fine chiama Object.create()
con la funzione costruttore. Ecco perché usiamo this
nei servizi.
Ok, quindi risulta che, non importa cosa usiamo, service()
o factory()
, è sempre una fabbrica che viene chiamata che crea un provider per il nostro servizio. Il che ci porta alla domanda più frequente nella storia di Angular: Quale dovrei usare?
Quale usare?
Fare questa domanda su internet ci porta a un paio di articoli e risposte di StackOverflow. Il primo è questa risposta. Dice:
“Fondamentalmente la differenza tra il servizio e la fabbrica è la seguente:”
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 + "!"; } }});
Ora sappiamo già cosa succede dietro le quinte, ma questa risposta aggiunge un altro commento. Dice che possiamo eseguire del codice prima di restituire il nostro oggetto letterale. Questo fondamentalmente ci permette di fare alcune cose di configurazione o di creare condizionatamente un oggetto o meno, cosa che non sembra essere possibile quando si crea un servizio direttamente, che è il motivo per cui la maggior parte delle risorse consiglia di utilizzare le fabbriche rispetto ai servizi, ma il ragionamento è inapprezzabile.
E se ti dicessi che possiamo fare la stessa cosa anche con i servizi? Un servizio è una funzione costruttrice, tuttavia, questo non ci impedisce di fare del lavoro aggiuntivo e di restituire dei letterali di oggetto. Infatti, le funzioni costruttore in JavaScript possono restituire qualsiasi cosa vogliano. Quindi possiamo prendere il nostro codice di servizio e scriverlo in modo che sostanzialmente faccia la stessa identica cosa della nostra factory:
app.service('MyService', function () { // we could do additional work here too return { sayHello: function () { console.log('hello'); }; }});
Hoppla, e adesso? Ci siamo appena resi conto che, a seconda di come scriviamo i nostri servizi, non c’è più alcuna differenza tra i due. La grande domanda rimane: Quale dovremmo usare?
I servizi ci permettono di usare le classi ES6
Ovviamente, scrivere i servizi in quel modo è un po’ controproducente, poiché è chiamato come una funzione costruttrice, quindi dovrebbe anche essere usato come tale. C’è qualche vantaggio rispetto all’altro allora? Sì, c’è. Si scopre che in realtà è meglio usare i servizi dove possibile, quando si tratta di migrare a ES6. La ragione è semplicemente che un servizio è una funzione costruttrice e una factory no. Lavorare con le funzioni costruttore in ES5 ci permette di usare facilmente le classi ES6 quando migriamo a ES6.
Per esempio, possiamo prendere il nostro codice e riscriverlo in ES6 come questo:
class MyService { sayHello() { console.log('hello'); }}app.service('MyService', MyService);
Una classe ES6 è davvero solo una funzione costruttore in ES5. Ne abbiamo scritto in Using ES6 with Angular today, se non avete ancora letto quell’articolo, vi consiglio di controllare.
Con le factory, questo non è possibile perché sono semplicemente chiamate come funzioni. Spero che questo articolo abbia chiarito tutto e incoraggi le persone a non usare le factory piuttosto che i servizi, se non sanno cosa usare.
Questo e altro si impara nella nostra Angular Master Class!