Załóżmy, że mamy następujący prosty kod z obsługą kontrolera MainCtrl
i serwisu dataService
.
Jak widać, dataService
wstrzyknięty do kontrolera, dostarcza jedną funkcję zwracającą „hardkodowaną” tablicę obiektów items
. W dalszej części zasymuluję pobieranie danych z serwera za pomocą opóźnienia uzyskanego przez wbudowany serwis $timeout
.
(function() { var app = angular.module('plunker', []); app .controller('MainCtrl', MainCtrl) .factory('dataService', ['$q', '$timeout', dataService]); function MainCtrl(dataService) { var vm = this; vm.name = 'World'; vm.items = dataService.getAllItems(); } function dataService($q, $timeout) { return { getAllItems: getAllItems }; function getAllItems() { var booksArray = [{ 'item': 1, 'name': 'item 1', }, { 'item': 2, 'name': 'item 2', }, { 'item': 3, 'name': 'item 3', }, { 'item': 4, 'name': 'item 4', }]; return booksArray; } } })()
W widoku zawartość zmiennych obejrzymy za pomocą:
<body ng-controller="MainCtrl as mc"> <p>Hello {{mc.name}}!</p> {{ mc.items }} </body>
Symulacja pobierania danych z serwera
Każde pobieranie danych z serwera wiąże się z pewnym asynchronicznym opóźnieniem. W naszym przykładzie opóźnienie to uzyskuje poprzez użycie serwisu $timeout
. Poniższy kod przedstawia modyfikację serwisu dataService
:
function dataService($q, $timeout) { return { getAllItems: getAllItems }; function getAllItems() { // ... // tablica obiektów itemsArray // ... $timeout(function() { console.log('dump array items: ', itemsArray); return itemsArray; }, 1000); } }
W rezultacie w widoku otrzymamy wynik nieco uboższy – {{mc.items}}
nie zwróci ciągu JSON, ponieważ w momencie renderowania widoku tablica ta była pusta.
Dlaczego? Mówiąc najprościej, cały program wykonał się w ułamku sekundy i „nie poczekał” za dane pobierane asynchroniczne z backendu, których pobranie wymaga pewnego czasu (w tym przypadku czasu stałego 1s). W konsoli (np. Firebug) można zauważyć, że dump array items
pojawi się dopiero 1s po zakończeniu wykonywania całego programu.
Jak zapewnić, że dane zostaną pobrane i zaprezentowane w widoku niezależnie od czasu ich pobierania?
Są 2 sposoby. Pobieranie synchroniczne (blokujące) lub obietnice (promises). My zajmiemy się tym tym drugim.
Obietnica dostarczenia danych z serwera
Zmodyfikujmy kod dodając obsługę promise $q
:
(function() { var app = angular.module('plunker', []); app .controller('MainCtrl', MainCtrl) .factory('dataService', ['$q', '$timeout', dataService]); function MainCtrl(dataService) { var vm = this; vm.name = 'World'; console.log('check 1'); dataService .getAllItems() .then( getItemsSuccess, getItemsFailure ); console.log('check 2'); // getAllItems() success handler function getItemsSuccess(items) { vm.items = items; console.log('success - now me!'); } // getAllItems() error handler function getItemsFailure(error) { console.log('failure - now me!'); console.log(error); } } function dataService($q, $timeout) { return { getAllItems: getAllItems }; function getAllItems() { // ... // tablica obiektów itemsArray // ... var deferred = $q.defer(); $timeout(function() { var successful = true; if (successful) { deferred.resolve(itemsArray); } else { deferred.reject('Error retriving Items'); } }, 1000); return deferred.promise; } } })()
W dataService
został utworzony obiekt deferred
, który gwarantuje (obiecuje) zwrócenie wyniku (jakiegokolwiek) w jednym z dwóch przypadków: rozwiązania (resolve
) lub odrzucenia (reject
). Niezależnie, po jakim czasie nastąpi resolve
lub reject
, deferred
zapewni, że dataService.getAllItems()
zwróci rezultat do dalszego obsłużenia w kontrolerze.
Obsługa w kontrolerze sprowadza się do wywołania metody .then()
obsługującej 2 metody anonimowe lub – tak jak w naszym przypadku – getItemsSuccess()
wywoływaną w przypadku powodzenia i getItemsFailure()
wywoływaną w przypadku wystąpienia błędu (wywołanie .reject
można zasymulować ustawieniem zmiennej succcessful = false
).