AngularJS #8 – Factory vs Service vs Provider

AngularJS



AngularJS daje nam możliwość pisania kodu na wiele różnych sposobów. Jedną z tendencji jaką zauważam na StackOverflow jest to, że ludzie starają się upchać do swoich kontrolerów wszystko co tylko możliwe – łącznie z logiką biznesową, pobieraniem / zapisywaniem danych, operacjach na DOM, czy przetwarzaniu wielkich struktur danych. Jest to wprawdzie domena tych mniej doświadczonych, jednak warto by każdy starał się odchudzać swoje kontrolery już na etapie pisania pierwszego kodu, a nie na etapie refaktoringu lub później (albo w ogóle!).

Kontrolery

Kontrolery w AngularJS mają to do siebie, że tworzone są (umieszczane w pamięci przeglądarki) w ramach tego jak są potrzebne i usuwane, gdy przestają być używane. Wyobraźmy sobie przechodzenie pomiędzy różnymi stronami naszej aplikacji, pomiędzy różnymi definicjami routingu, gdzie dla każdej strony wywoływany jest osobny kontroler. Następnie wyobraźmy sobie, że wszystkie elementy w kontrolerach tworzone i usuwane są za każdym takim przejściem ze strony do strony. Jest to niebywałe marnotrastwo zasobów, co wpływa także na wydajność pisanej przez nas aplikacji. Całą tą logikę odpowiedzialną chociażby za pobieranie danych lub ich zapisywanie po stronie API (backendu) jesteśmy w stanie przenieść do osobnych serwisów, które to wstrzyknięte do naszego kontrolera pozwolą zrealizować dokładnie taką samą funkcjonalność. Serwisy mają tą przewagę, że tworzone są jednorazowo (jako singletony) i wstrzykiwane mogą być jako zależność do wielu kontrolerów.

Factory vs Service vs Provider

Problem może pojawić się na etapie wyboru odpowiedniej drogi, która pozwoli nam zrealizować daną funkcjonalność. AngularJS daje nam obecnie trzy sposoby na odchudzenie naszych kontrolerów poprzez użycie:

  • Factory
  • Service
  • Provider

Poniżej postaram się omówić każdy z nich oraz wskazać różnice, które mogą zadecydować o konkretnym wyborze. Przykłady będą bajecznie proste, więc z pewnością każdy poradzi sobie z ich przyswojeniem. Celowo nie wykorzystuję tutaj $http, czy $resource, by nie komplikować i zaciemniać kodu. Znając oba mechanizmy możecie posłużyć się nimi do zbudowania pełnoprawnego kodu serwisu, który obsługiwać będzie logikę biznesową Waszej aplikacji. Przechodząc jednak do konkretów..

1 ) Factory

Factory to w zasadzie najprostsza wersja serwisów w AngularJS. Tworząc factory, tworzymy de facto obiekt z polami w formie zwykłych zmiennych lub funkcji, który następnie zwracamy poprzez użycie return. Wstrzyknięte do kontrolera factory jest dokładnie takim prostym obiektem. Posłużę się przykładem, który lepiej zilustruje to co mam na myśli.

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/69YQ8/1/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Spójrzcie od razu na zakładkę JavaScript. Stworzyłem tam naiwny kontroler, którego jedynym zadaniem jest ustawienie nazwy użytkownika poprzez wywołanie metody setUsername() serwisu User wstrzykniętego jako dependency do kontrolera oraz wywołanie metody sayHello() tegoż serwisu w celu pobrania wiadomości powitalnej dla ustawionej nazwy użytkownika.

Poza kontrolerem zdefiniowany został serwis User typu factory. W ciele tego serwisu mamy trzy zmienne lokalne (_username, _setUsername, _sayHello), które widoczne są tylko w ramach tego serwisu. Następnie zwracany jest nowy obiekt za pomocą polecenia return z dwoma polami (setUsername, sayHello), do których przypisane zostają referencje zmiennych lokalnych (funkcji), odpowiednio _setUsername oraz _sayHello. Równie dobrze jednak, nasz serwis factory mógłby wyglądać tak:

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/69YQ8/2/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Ograniczamy się w tym momencie do jednej zmiennej lokalnej w kontrolerze User.

Poprzez wstrzyknięcie factory User do kontrolera, otrzymujemy w nim dostęp do pól zwróconego obiektu. Działanie jest tak proste, że nie zamierzam tego komplikować kolejnymi wyjaśnieniami.

2) Service

Kolejny serwis nazwany został dosłownie Service. W porównaniu do FactoryService tworzony jest na zasadzie użycia słowa new, a następnie zostaje wstrzyknięty do kontrolera. Użycie konstruktora (new) powoduje, że wszystkie widoczne w serwisie zmienne przypisane muszą zostać do słowa this, które widoczne jest po wstrzyknięciu serwisu do kontrolera. Spójrzcie na ten sam przykład, ale z wykorzystaniem Service.

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/69YQ8/3/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Widać wyraźnie, że kod po stronie kontrolera pozostał taki sam. Serwis natomiast wygląda nieco inaczej. Nie ma już słowa return i zwracanego w ten sposób obiektu z metodami setUsername oraz sayHello. Zamiast tego, metody te przypisane zostały do słowa kluczowego this, które zwrócone zostanie do kontrolera w ramach wywołania konstruktora przy użyciu new przez $injector.

3) Provider

Provider jest jedynym serwisem, który użyty może być w ramach config naszej aplikacji. Dzięki temu jesteśmy w stanie dokonać ustawień naszego serwisu jeszcze zanim zostanie on wstrzyknięty i użyty w kontrolerze. Struktura provider jest nieco rozdmuchana, ale w gruncie rzeczy przypomina on factory oraz service. Poniżej ciągle ten sam przykład, ale z wykorzystaniem provider.

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/69YQ8/4/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Zmian jest sporo. Na samym końcu zobaczyć możecie nową sekcję: config. Służy ona do ustawiania parametrów naszej aplikacji, a w tym wypadku do ustawienia pola naszego serwisu. W samym provider mamy teraz zmienną _username przypisaną do słowa this i tylko takie zmienne widoczne są z poziomu config aplikacji. Następnie do this przypisana zostaje funkcja $get, która z kolei zwraca nam prosty obiekt za pomocą słowa return do kontrolera. Tylko obiekt zwrócony przez funkcję $get naszego providera widoczny jest po tej drugiej stronie (kontrolerze). W kontrolerze ubyło ustawianie nazwy użytkownika, która przeniesiona została do części config. Warto zauważyć, że do ciała funkcji config wstrzykiwany jest UserProvider, a nie User. Zapamiętajcie, że w ten sposób możemy dobrać się do pól przypisanych do this naszego providera. Gdybyśmy nasz provider nazywał się np. myProvider, to do config musielibyśmy wstrzyknąć myProviderProvider. Za każdym razem jest to dodanie słowa Provider do oryginalnej nazwy. Pozostaje również zapamiętać, że tylko serwis provider pozwala nam na ustawienie zmiennych w sekcji config aplikacji.

Krótko podsumowując

Tym oto prostym sposobem udało mi się (mam nadzieję) pokazać zasadnicze różnice pomiędzy różnymi typami serwisów w AngularJS. Zapraszam tym samym do podzielenia się swoimi wnioskami w komentarzach oraz do odchudzania swoich kontrolerów.