Wacht, wat? Weer een artikel dat de grote vraag beantwoordt: Service vs Factory, wat moet ik gebruiken? Ja, het lijkt erop dat dit niet meer nodig is, omdat er een ton aan bronnen op het internet zijn die dat onderwerp bespreken. Het blijkt dat deze vraag nog steeds elke week of zo opduikt op verschillende kanalen, en zelfs na het lezen van de top tien antwoorden op StackOverflow, is het nog steeds niet erg duidelijk. Desondanks blijkt ook dat de huidige bronnen op het web de eigenlijke best practice niet echt promoten, zeker als we de recente bewegingen van het webplatform in beschouwing nemen. ES6 I’m looking at you!
Dit artikel legt voor eens en altijd het verschil uit tussen services en factories en waarom we services willen verkiezen boven factories.
Het verschil tussen services en factories
Okay, dus wat is het verschil tussen een service en een factory in AngularJS? Zoals we allemaal weten, kunnen we een service als volgt definiëren:
app.service('MyService', function () { this.sayHello = function () { console.log('hello'); };});
.service()
is een methode op onze module die een naam neemt en een functie die de service definieert. Vrij eenvoudig. Eenmaal gedefinieerd, kunnen we die specifieke service injecteren en gebruiken in andere componenten, zoals controllers, directives en filters, zoals dit:
app.controller('AppController', function (MyService) { MyService.sayHello(); // logs 'hello'});
Okay, duidelijk. Nu hetzelfde als een fabriek:
app.factory('MyService', function () { return { sayHello: function () { console.log('hello'); } }});
Opnieuw, .factory()
is een methode op onze module en het neemt ook een naam en een functie, die de fabriek definieert. We kunnen dat ding op precies dezelfde manier injecteren en gebruiken als we met de service hebben gedaan. Wat is het verschil hier?
Wel, je zou kunnen zien dat in plaats van te werken met this
in de fabriek, we een object letterlijk teruggeven. Waarom is dat? Het blijkt, een service is een constructor functie, terwijl een fabriek dat niet is. Ergens diep in deze Angular wereld, is er deze code die Object.create()
aanroept met de service constructor functie, wanneer het wordt geïnstantieerd. Maar een fabrieksfunctie is eigenlijk gewoon een functie die wordt aangeroepen, en daarom moeten we expliciet een object retourneren.
Om dat een beetje duidelijker te maken, kunnen we gewoon een kijkje nemen in de Angular broncode. Hier is hoe de factory()
functie eruit ziet:
function factory(name, factoryFn, enforce) { return provider(name, { $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn });}
Het neemt de naam en de fabrieksfunctie die is doorgegeven en retourneert in principe een provider met dezelfde naam, die een $get
methode heeft die onze fabrieksfunctie is. Dus wat is het met dit provider ding? Nou, wanneer je de injector om een specifieke afhankelijkheid vraagt, vraagt hij in principe de corresponderende provider om een instantie van die service, door de $get()
methode aan te roepen. Daarom is $get()
vereist bij het aanmaken van providers.
Met andere woorden, als we MyService
ergens injecteren, gebeurt er achter de schermen het volgende:
MyServiceProvider.$get(); // return the instance of the service
Okee, de fabrieksfuncties worden gewoon aangeroepen, hoe zit het met de service code? Hier is nog een fragment:
function service(name, constructor) { return factory(name, );}
Oh kijk, het blijkt dat wanneer we service()
oproepen, het eigenlijk factory()
oproept. Maar het geeft niet alleen onze service constructor functie door aan de fabriek zoals het is. Het geeft een functie door die de injector vraagt om een object te instantiëren met de gegeven constructor. Met andere woorden: een service roept een voorgedefinieerde fabriek aan, die eindigt als $get()
methode op de overeenkomstige provider. $injector.instantiate()
is de methode die uiteindelijk Object.create()
aanroept met de constructor-functie. Daarom gebruiken we this
in services.
Okee, het blijkt dus dat, ongeacht wat we gebruiken, service()
of factory()
, het altijd een fabriek is die wordt aangeroepen die een provider voor onze service maakt. Dat brengt ons bij de meest gestelde vraag in de Angular geschiedenis: Welke moet ik gebruiken?
Welke moet ik gebruiken?
Het stellen van die vraag op het internet brengt ons bij een paar artikelen en StackOverflow antwoorden. De eerste is dit antwoord. Het zegt:
“In principe is het verschil tussen de service en de factory als volgt:”
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 + "!"; } }});
We weten nu al wat er achter de schermen gebeurt, maar dit antwoord voegt nog een opmerking toe. Er staat dat we code kunnen uitvoeren voordat we ons object literal teruggeven. Dat stelt ons in principe in staat om wat configuratiedingen te doen of een object voorwaardelijk te maken of niet, wat niet mogelijk lijkt te zijn bij het direct maken van een service, wat de reden is waarom de meeste bronnen aanbevelen om factories boven services te gebruiken, maar de redenering is niet te waarderen.
Wat als ik je zou vertellen dat we met services ook precies hetzelfde kunnen doen?
Jaap, juist. Een service is een constructor functie, maar dat weerhoudt ons er niet van om extra werk te doen en object literals terug te geven. In feite kunnen constructorfuncties in JavaScript teruggeven wat ze maar willen. Dus we kunnen onze servicecode zo schrijven dat hij in principe precies hetzelfde doet als onze fabriek:
app.service('MyService', function () { // we could do additional work here too return { sayHello: function () { console.log('hello'); }; }});
Hoppla, dus wat nu? We realiseerden ons net dat, afhankelijk van hoe we onze services schrijven, er helemaal geen verschil meer is tussen de twee. De grote vraag blijft: Welke moeten we gebruiken?
Services stellen ons in staat ES6 classes te gebruiken
Op die manier services schrijven is natuurlijk nogal contra-productief, want het wordt aangeroepen als een constructor-functie, dus moet het ook als zodanig gebruikt worden. Is er dan enig voordeel ten opzichte van het andere? Ja, dat is er. Het blijkt dat het eigenlijk beter is om services te gebruiken waar mogelijk, als het gaat om het migreren naar ES6. De reden daarvoor is simpelweg dat een service een constructor functie is en een factory niet. Het werken met constructorfuncties in ES5 stelt ons in staat om gemakkelijk ES6-klassen te gebruiken wanneer we naar ES6 migreren.
We kunnen bijvoorbeeld onze code nemen en die in ES6 als volgt herschrijven:
class MyService { sayHello() { console.log('hello'); }}app.service('MyService', MyService);
Een ES6-klasse is eigenlijk gewoon een constructorfunctie in ES5. We hebben daar vandaag over geschreven in Using ES6 with Angular, als je dat artikel nog niet hebt gelezen, raad ik je aan om het te lezen.
Met factories is dit niet mogelijk omdat ze gewoon als functies worden aangeroepen. Ik hoop dat dit artikel alles duidelijk heeft gemaakt en mensen aanmoedigt om geen factories boven services te gebruiken, als ze niet weten wat ze moeten gebruiken.
Dit en meer leer je in onze Angular Master Class!