Angular JS: Usługi – factory, service, provider – porównanie część 4

Jedną z podstawowych czynności w modelu Model-Widok-Kontroler jest rozdzielenie warstwy danych od warstwy prezentacji. I tak w warstwie prezentacji nie powinno być żadnych zmian w modelu, widok powinien jedynie wyświetlać dane. Idąc dalej, kontroler powinien łączyć warstwę modelu z widokiem, jednakże także sam nie powinien modyfikować modelu bezpośrednio, a korzystać z dodatkowych obiektów, najlepiej luźno połączonych za pomocą DI.

Funkcja factory

Podobnie w przypadku Angulara i naszej ostatniej aplikacji, nasz kod kontrolera nie powinien bezpośrednio pobierać danych z zewnętrznego źródła, wszelkie operacje na tym źródle najlepiej wyciągnąć na zewnątrz kontrolera.

W taki właśnie sposób spróbujemy przerobić kod z poprzedniego wpisu. Oczywiście funkcjonalność zostanie całkowicie zachowana, czyli nasz program pobierze z API listę postów, a następnie przekaże ją do widoku.

Przyjrzyjmy się strukturze nowego projektu:
views/posts.html – widok, w którym będziemy wyświetlać listę z postami pobranymi z API

js/postsController.js – nasz kontroler, który w założeniu ma wykorzystać usługę do pobrania postów z API

js/service.js – nasza usługa realizująca dostęp do API

I właśnie w pierwszej kolejności przyjrzyjmy się naszej usłudze:

Kod naszej usługi rozpoczyna się od zadeklarowania nowego modułu o nazwie ‘service’, w trakcie jego tworzenia wykorzystaliśmy również metodę o nazwie factory. Metoda ta pozwoliła nam na stworzenie usługi o nazwie ‘posts‘, a także przypisaniu do niej funkcji postsFunction, która za parametr przyjmie usługę $http niezbędną do wykonania żądania do naszego API. Warto również zauważyć, iż usługę wbudowaną $http dodaliśmy jako zależność do naszej funkcji fabryki, usługa tam będzie niezbędna przy wykonywaniu żądania do API.
W ciele funkcji postsFunction definiujemy co nasza usługa ma zwracać, jakie metody udostępniać. Jedyną zdefiniowaną metodą dostępną w obiekcie usługi będzie metoda getData(). Metoda ta wykona żądanie HTTP do serwera API, a następnie zwrócone przez API dane przekaże do funkcji przekazanej w parametrze. Dzięki temu nie kodujemy na twardo co ma się wydarzyć z naszymi danymi, a ich docelowe użycie jest sprawą otwartą i zależną tylko od funkcji przekazanej do metody getData().

Reasumując, nasza usługa o rozbudowanej nazwie ‘posts‘ udostępnia jedną funkcję (chociaż nic nie stoi na przeszkodzie by udostępniała szereg funkcji), która to funkcja getData za parametr przyjmuje funkcję, która zostanie wywołana na danych, pobranych wcześniej z API, za pomocą usługi $http.

Jest to może w jakiś sposób skomplikowane, aczkolwiek po powtórnym przeanalizowaniu kodu i jego opisu wszystko powinno być jasne.

Spróbujmy teraz wywołać naszą usługę, robiąc to bezpośrednio z naszego kontrolera (postsController.js):

Tworzymy nowy moduł o nazwie PostsApp, wstrzykujemy naszą usługę, a następnie tworzymy kontroler PostsController, do którego przekazujemy naszą niedawno stworzoną usługę, a w którego treści umieszczamy wywołanie metody getData() z przekazaniem funkcji będącej wywołaniem zwrotnym, który ma pobrany rezultat przypisać do pola items zmiennej postsfromapiservice.

Efekt działania kodu jest ten sam – pobrane rekordy z API. Diametralna różnica uwidacznia się w samym kodzie, wyniesienie pobierania danych, dostępu do danych do oddzielnej usługi skutkuje zmniejszeniem objętości kontrolera, lepszą strukturyzacją poszczególnych elementów kodu, co wiąże się z zachowaniem wzorca MVC.

W tym miejscu warto zwrócić uwagę na sposób deklaracji nowych usług. W rozpatrywanym powyżej przypadku usługę stworzyliśmy za pomocą metody factory():

Funkcja factory() tworzy obiekt, do którego przypisywane są konkretne metody (w naszym przypadku była to jedna metoda getData()). Obiekt ten jest zwracany do kontrolera, który to może korzystać z dobrodziejstw zaimplementowanych w danym obiekcie metod. Tak jak opisałem powyżej, pozwala to na odseparowanie części logiki od kontrolera, w naszym przypadku, odseparowaniu uległ dostęp do danych, aczkolwiek może być to dowolny fragment kodu, którego użycie może być rozsiane po całym projekcie. Takie rozwiązanie sprzyja tworzeniu kodu, który może być wielokrotnie użyty, a przy tym modyfikowany tylko w jednym miejscu.

Fabryka może również zwracać konstruktor:

service.js:

Wywołanie takiej usługi wymaga jawnego stworzenia nowego obiektu:

postsController.js:

W linijce (11) widzimy stworzenie nowego obiektu.

Funkcja service

Drugim sposobem tworzenia usług jest użycie funkcji ‘service()’. Zastępuje ona dotychczas poznaną metodę ‘factory()’, a różnica polega na tym, iż zamiast przekazanego do kontrolera obiektu usługi, przekazywany jest konstruktor, który to dopiero tworzy obiekt z potrzebnymi w danym momencie funkcjonalnościami. Tworzenie obiektu może odbyć się poprzez nie jawne użycie słowa kluczowego ‘new’. Wszystko to robi za nas Angular.

Kod usługi tworzonej za pomocą ‘service()’ prezentuje się następująco:

service.js:

Widzimy, iż funkcja postsFunction() nie zwraca już jawnie obiektu, a raczej przesyła konstruktor, za pomocą którego można tworzyć obiekt z jego pełną funkcjonalnością. Wywołanie tego typu usługi nie wymaga żadnych zmian w pliku postsController.js, ale należy być świadomym, iż Angular w sposób niejawny używa naszego kontrolera i tworzy nowy obiekt.

Różnica pomiędzy service a factory jest bardzo subtelna, przyjęło się, że service używa się w wszelkiego rodzaju obiektach typu utils, które wspierają system swoimi funkcjonalnościami.

Funkcja provider

Ostatnim sposobem tworzenia usług jest użycie funkcji provider(), funkcja różni się tym, iż daje programiście pełną swobodę w aspekcie sposobu kreowania i konfiguracji obiektu. W praktyce oznacza to, że w momencie tworzenia usługi możemy dodać swoje opcje konfiguracyjne. W naszym przypadku spróbujmy wyciągnąć adres url API do opcji konfiguracyjnych:

service.js:

Pierwsze co rzuca się w oczy to oczywiście zmiana factory i service na provider. Widzimy również, że nasza usługa będzie zwracała znaną nam funkcję getData (do pobrania danych z API), pośrednikiem udostępniania będzie natomiast funkcja $get. Co warto zaznaczyć, zmieniło się miejsce wstrzykiwania wbudowanego modułu $http. Niestety ale nie możemy tego zrobić, tak jak do tej pory w factory lub service, czy jak w tym analogicznym przypadku provider, gdyż ów provider w tym danym momencie jest jeszcze w fazie inicjalizacji/konfiguracji. Na tym etapie moduł $http nie jest dostępny.
Wstrzyknięcie tego modułu przenosimy więc do funkcji $get.

Samo użycie tak zdefiniowanej usługi nie jest niczym trudnym:
postsController.js:

Widzimy, iż jedyna różnica to użycie funkcji config, w której parametrze podajemy nazwę naszej usługi z dopiskiem Provider (czyli “posts”+”Provider”=”postsProvider“).

Za pomocą zaimplementowanej metody setUrl podajemy adres naszego api, czyli rezultat, na którym nam najbardziej zależało.

W ten oto sposób dobrnęliśmy do końca implementacji providera. Oczywiście już na pierwszy rzut oka widać, iż używa się go wszędzie tam, gdzie najważniejsza jest duża konfigurowalność usługi, dostępna jeszcze przed utworzeniem właściwego obiektu.

Inne usługi

Oprócz możliwości tworzenia usług Angular posiada ogromną bazę własnych usług, gotowych do wykorzystania bez specjalnego przygotowania, są to m.in.:
$http – usługa do tworzenia żądań HTTP

$animate – usługa do tworzenia animacji

$document – usługa dostarczająca informacje o elementach DOM

$filter – usługa udostępniająca filtry

$route – usługa zmiany bieżącego widoku

$timeout – usługa umożliwiająca manipulacje momentem wywołania kodu (setTimeout())

$log – logowanie wiadomości

i wiele innych.

Podsumowanie

To już końcówka trochę przydługiego wpisu, do tej pory skupiłem się na rzeczach dzielących i różniących sposoby tworzenia serwisów. Warto również powiedzieć, co je wszystkie łączy, a łączy je między innymi fakt, że wszystkie one są Singletonami. Tworzone są raz, dokładnie w momencie gdy są potrzebne, a następnie cały czas dostępne w jednej, niezmienionej wersji. Warto o tym pamiętać używając tych samych serwisów w różnych modułach.

Tabela podsumowująca:

Tworzenie usługi poprzez  DEMO Kod źródłowy Dodatkowe
Factory zwracające obiekt DEMO ŹRÓDŁO
Factory zwracające konstruktor DEMO ŹRÓDŁO
Service DEMO ŹRÓDŁO
Provider DEMO ŹRÓDŁO dodatkowa konfiguracja usługi przed jej stworzeniem

Czytając różne opracowania w internecie, można natknąć się na statystyki mówiące, iż w praktyce 80% tworzonych usług tworzona jest przez użycie factory. Niezależnie od wybranego sposobu implementacji, warto jest podzielić tworzony system tak, by efektywnie korzystał z usług. Czyni to projektowaną aplikację, przejrzystszą, łatwiejszą do analizy i co ważniejsze, do automatycznego testowania.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

This site uses Akismet to reduce spam. Learn how your comment data is processed.