Czekajcie, co? Kolejny artykuł, który odpowiada na wielkie pytanie: Service vs Factory, czego powinienem używać? Tak, wydaje się, że nie jest to już potrzebne, bo w internecie jest mnóstwo zasobów, które poruszają ten temat. Okazuje się, że to pytanie nadal wyskakuje co tydzień lub tak na różnych kanałach, a nawet po przeczytaniu dziesięciu najlepszych odpowiedzi na StackOverflow, to nadal nie jest bardzo jasne. Mimo to wydaje się również, że obecne zasoby w sieci nie promują rzeczywistych najlepszych praktyk, szczególnie jeśli weźmiemy pod uwagę ostatnie ruchy platformy internetowej. ES6 I’m looking at you!
Ten artykuł wyjaśnia raz na zawsze różnicę między usługami a fabrykami i dlaczego chcemy preferować usługi nad fabrykami.
Różnica między usługami a fabrykami
Okay, więc jaka jest różnica między usługą a fabryką w AngularJS? Jak wszyscy wiemy, możemy zdefiniować usługę w ten sposób:
app.service('MyService', function () { this.sayHello = function () { console.log('hello'); };});
.service()
to metoda na naszym module, która pobiera nazwę i funkcję definiującą usługę. Całkiem prosto. Po zdefiniowaniu, możemy wstrzykiwać i używać tej konkretnej usługi w innych komponentach, takich jak kontrolery, dyrektywy i filtry, w ten sposób:
app.controller('AppController', function (MyService) { MyService.sayHello(); // logs 'hello'});
Okay, jasne. Teraz to samo, co z fabryką:
app.factory('MyService', function () { return { sayHello: function () { console.log('hello'); } }});
Ponownie, .factory()
jest metodą na naszym module i również pobiera nazwę i funkcję, która definiuje fabrykę. Możemy wstrzyknąć i użyć tej rzeczy dokładnie tak samo, jak zrobiliśmy to z usługą. Teraz jaka jest różnica?
Cóż, możesz zauważyć, że zamiast pracować z this
w fabryce, zwracamy dosłowny obiekt. Dlaczego tak jest? Okazuje się, że usługa jest funkcją konstruktora, podczas gdy fabryka nie. Gdzieś głęboko w tym świecie Angulara, jest ten kod, który wywołuje Object.create()
z funkcją konstruktora usługi, kiedy jest ona instancjonowana. Jednak funkcja fabryki jest tak naprawdę tylko funkcją, która zostaje wywołana, dlatego musimy zwrócić obiekt jawnie.
Aby to trochę bardziej wyjaśnić, możemy po prostu spojrzeć na kod źródłowy Angulara. Oto jak wygląda funkcja factory()
:
function factory(name, factoryFn, enforce) { return provider(name, { $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn });}
Przyjmuje ona nazwę i funkcję fabryki, która jest przekazywana i w zasadzie zwraca dostawcę o tej samej nazwie, który ma metodę $get
, która jest naszą funkcją fabryki. Więc o co chodzi z tym dostawcą? Cóż, za każdym razem gdy poprosimy injector o konkretną zależność, w zasadzie zapyta on odpowiedniego providera o instancję tej usługi, poprzez wywołanie metody $get()
. Dlatego właśnie $get()
jest wymagane podczas tworzenia providerów.
Innymi słowy, jeśli wstrzykniemy gdzieś MyService
, to co dzieje się za kulisami to:
MyServiceProvider.$get(); // return the instance of the service
Dobrze, funkcje fabryczne są po prostu wywoływane, a co z kodem usługi? Oto kolejny wycinek:
function service(name, constructor) { return factory(name, );}
O popatrz, okazuje się, że kiedy wywołujemy service()
, to tak naprawdę wywołuje on factory()
. Ale to nie jest po prostu przekazanie naszej funkcji konstruktora usługi do fabryki, tak jak jest. Przekazuje funkcję, która prosi injector o zainicjowanie obiektu przez dany konstruktor. Innymi słowy: usługa wywołuje predefiniowaną fabrykę, która kończy się jako metoda $get()
na odpowiednim providerze. $injector.instantiate()
jest metodą, która ostatecznie wywołuje Object.create()
z funkcją konstruktora. Dlatego właśnie używamy this
w usługach.
Okay, więc okazuje się, że bez względu na to, czego używamy, service()
czy factory()
, zawsze wywoływana jest fabryka, która tworzy dostawcę dla naszej usługi. Co prowadzi nas do najczęściej zadawanego pytania w historii Angulara: Which one should I use?
Which one to use?
Zadawanie tego pytania w internecie prowadzi nas do kilku artykułów i odpowiedzi StackOverflow. Pierwszą z nich jest ta odpowiedź. Mówi:
„Zasadniczo różnica między usługą a fabryką jest następująca:”
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 + "!"; } }});
Teraz już wiemy, co dzieje się za kulisami, ale ta odpowiedź dodaje kolejny komentarz. Mówi, że możemy uruchomić kod, zanim zwrócimy nasz obiekt dosłowny. To w zasadzie pozwala nam robić pewne rzeczy konfiguracyjne lub warunkowo tworzyć obiekt lub nie, co nie wydaje się być możliwe podczas bezpośredniego tworzenia usługi, dlatego większość zasobów zaleca używanie fabryk nad usługami, ale rozumowanie jest nie do przecenienia.
Co jeśli powiem ci, że możemy zrobić dokładnie to samo z usługami również?
Yeap, poprawnie. Usługa jest funkcją konstruktora, jednak to nie przeszkadza nam w wykonywaniu dodatkowej pracy i zwracaniu literałów obiektów. W rzeczywistości, funkcje konstruktora w JavaScript mogą zwracać co tylko chcą. Możemy więc wziąć nasz kod usługi i napisać go w taki sposób, że w zasadzie robi dokładnie to samo co nasza fabryka:
app.service('MyService', function () { // we could do additional work here too return { sayHello: function () { console.log('hello'); }; }});
Hoppla, i co teraz? Właśnie zdaliśmy sobie sprawę, że w zależności od tego, jak napiszemy nasze usługi, nie ma już żadnej różnicy między nimi. Pozostaje wielkie pytanie: Którego z nich powinniśmy używać?
Serwisy pozwalają nam na używanie klas ES6
Oczywiście pisanie serwisów w ten sposób jest jakby contra produktywne, ponieważ jest on wywoływany jako funkcja konstruktora, więc powinien być również tak używany. Czy w takim razie jest jakaś przewaga jednego nad drugim? Tak, jest. Okazuje się, że jeśli chodzi o migrację do ES6, to faktycznie lepiej jest używać usług tam, gdzie to możliwe. Powodem tego jest po prostu to, że usługa jest funkcją konstruktora, a fabryka nie. Praca z funkcjami konstruktora w ES5 pozwala nam łatwo używać klas ES6, gdy migrujemy do ES6.
Na przykład, możemy wziąć nasz kod i przepisać go w ES6 w ten sposób:
class MyService { sayHello() { console.log('hello'); }}app.service('MyService', MyService);
Klasa ES6 to tak naprawdę tylko funkcja konstruktora w ES5. Pisaliśmy o tym w Using ES6 with Angular today, jeśli jeszcze nie czytałeś tego artykułu, polecam sprawdzić to.
W przypadku fabryk nie jest to możliwe, ponieważ są one po prostu wywoływane jako funkcje. Mam nadzieję, że ten artykuł sprawił, że wszystko stało się jasne i zachęca ludzi do nieużywania fabryk nad usługami, jeśli nie wiedzą, czego użyć.
To i więcej dowiesz się w naszym Angular Master Class!
.