Vänta, vad? Ännu en artikel som svarar på den stora frågan: Service vs Factory, vad ska jag använda? Ja, det verkar som om detta inte behövs längre, eftersom det finns massor av resurser på internet som diskuterar detta ämne. Det visar sig att den här frågan fortfarande dyker upp varje vecka eller så på olika kanaler, och även efter att ha läst de tio bästa svaren på StackOverflow är det fortfarande inte särskilt tydligt. Trots det visar det sig också att de nuvarande resurserna på webben inte riktigt främjar den faktiska bästa praktiken, särskilt om vi tar hänsyn till de senaste rörelserna inom webbplattformen. ES6 I’m looking at you!
Denna artikel förklarar en gång för alla skillnaden mellan tjänster och fabriker och varför vi vill föredra tjänster framför fabriker.
Skillnaden mellan tjänster och fabriker
Okej, så vad är skillnaden mellan en tjänst och en fabrik i AngularJS? Som vi alla vet kan vi definiera en tjänst så här:
app.service('MyService', function () { this.sayHello = function () { console.log('hello'); };});
.service()
är en metod på vår modul som tar ett namn och en funktion som definierar tjänsten. Det är ganska enkelt. När den har definierats kan vi injicera och använda den specifika tjänsten i andra komponenter, som kontrollanter, direktiv och filter, så här:
app.controller('AppController', function (MyService) { MyService.sayHello(); // logs 'hello'});
Okej, klart. Nu samma sak som en fabrik:
app.factory('MyService', function () { return { sayHello: function () { console.log('hello'); } }});
Även .factory()
är en metod på vår modul och den tar också ett namn och en funktion, som definierar fabriken. Vi kan injicera och använda den saken på exakt samma sätt som vi gjorde med tjänsten. Vad är skillnaden här?
Ja, du kanske ser att istället för att arbeta med this
i fabriken så returnerar vi en objektlitteral. Varför är det så? Det visar sig att en tjänst är en konstruktorfunktion medan en fabrik inte är det. Någonstans djupt inne i Angular-världen finns det en kod som anropar Object.create()
med tjänstens konstruktorfunktion när den instansieras. Men en fabriksfunktion är egentligen bara en funktion som anropas, vilket är anledningen till att vi måste returnera ett objekt explicit.
För att göra detta lite tydligare kan vi helt enkelt ta en titt på Angular-källkoden. Så här ser factory()
-funktionen ut:
function factory(name, factoryFn, enforce) { return provider(name, { $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn });}
Den tar namnet och fabriksfunktionen som skickas vidare och returnerar i princip en provider med samma namn, som har en $get
-metod som är vår fabriksfunktion. Så vad är det med den här provider-grejen? Jo, när du ber injektorn om ett specifikt beroende frågar den i princip motsvarande provider efter en instans av den tjänsten genom att anropa $get()
-metoden. Det är därför $get()
krävs, när man skapar providers.
Med andra ord, om vi injicerar MyService
någonstans, så händer följande bakom kulisserna:
MyServiceProvider.$get(); // return the instance of the service
Okej, fabriksfunktionerna blir bara anropade, hur är det med tjänstens kod? Här är ett annat utdrag:
function service(name, constructor) { return factory(name, );}
Oh titta, det visar sig att när vi anropar service()
så anropar den faktiskt factory()
. Men den skickar inte bara vår tjänstekonstruktorfunktion till fabriken som den är. Den passerar en funktion som ber injektorn att instantiera och objekt genom den givna konstruktören. Med andra ord: en tjänst anropar en fördefinierad fabrik, som slutar som $get()
-metod på motsvarande provider. $injector.instantiate()
är den metod som i slutändan anropar Object.create()
med konstruktorfunktionen. Det är därför vi använder this
i tjänster.
Okej, så det visar sig att oavsett vad vi använder, service()
eller factory()
, är det alltid en fabrik som anropas som skapar en provider för vår tjänst. Vilket för oss till den mest ställda frågan i Angular-historien: Vilken ska jag använda?
Vilken ska jag använda?
Att ställa den frågan på internet leder oss till ett par artiklar och StackOverflow-svar. Den första är det här svaret. Det säger:
”I grund och botten är skillnaden mellan tjänsten och fabriken följande:”
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 + "!"; } }});
Vi vet nu redan vad som händer bakom kulisserna, men det här svaret lägger till ytterligare en kommentar. Det står att vi kan köra kod innan vi returnerar vår objektlitteratur. Det gör det i princip möjligt för oss att göra några konfigurationsgrejer eller villkorligt skapa ett objekt eller inte, vilket inte verkar vara möjligt när man skapar en tjänst direkt, vilket är anledningen till att de flesta resurser rekommenderar att man använder fabriker framför tjänster, men resonemanget är otacksamt.
Tänk om jag sa att vi kan göra exakt samma sak med tjänster också?
Yeap, korrekt. En tjänst är en konstruktorfunktion, men det hindrar oss inte från att göra ytterligare arbete och returnera objektlitteraler. Faktum är att konstruktorfunktioner i JavaScript kan returnera vad de vill. Så vi kan ta vår tjänstekod och skriva den så att den i princip gör exakt samma sak som vår fabrik:
app.service('MyService', function () { // we could do additional work here too return { sayHello: function () { console.log('hello'); }; }});
Hoppla, så vad händer nu? Vi insåg just att beroende på hur vi skriver våra tjänster finns det ingen skillnad mellan de två alls längre. Den stora frågan kvarstår:
Tjänster gör det möjligt för oss att använda ES6-klasser
Självklart är det lite kontraproduktivt att skriva tjänster på det sättet, eftersom det kallas som en konstruktorfunktion, så det bör också användas som en sådan. Finns det någon fördel jämfört med det andra överhuvudtaget då? Ja, det finns det. Det visar sig att det faktiskt är bättre att använda tjänster när det är möjligt, när det gäller att migrera till ES6. Anledningen till det är helt enkelt att en tjänst är en konstruktorfunktion och att en fabrik inte är det. Genom att arbeta med konstruktorfunktioner i ES5 kan vi enkelt använda ES6-klasser när vi migrerar till ES6.
Till exempel kan vi ta vår kod och skriva om den i ES6 så här:
class MyService { sayHello() { console.log('hello'); }}app.service('MyService', MyService);
En ES6-klass är egentligen bara en konstruktorfunktion i ES5. Vi skrev om det i Using ES6 with Angular today, om du inte har läst den artikeln ännu rekommenderar jag att du kollar upp den.
Med factories är detta inte möjligt eftersom de helt enkelt kallas som funktioner. Jag hoppas att den här artikeln klargjorde allt och uppmuntrar folk att inte använda factories framför services, om de inte vet vad de ska använda.
Detta och mer lär du dig i vår Angular Master Class!