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).