W poprzednim wpisie poznaliśmy teorię na temat serwisów i proste przykłady. Teraz możemy je zastosować w praktyce w naszej aplikacji, aby zachowywać dane podczas przechodzenia pomiędzy zakładkami. W przyszłości będzie nam to potrzebne, aby dodać różne funkcjonalności na różnych zakładkach (np. import danych). W tym wpisie dodamy tylko zakładkę z wynikiem tekstowym ankiety, aby przetestować utworzony serwis.
Tworzenie serwisu
Na nasze potrzeby wybraliśmy serwis typu Service (wygodnie się go używa poprzez this
, a początkowa konfiguracja dostępna w Provider nie jest nam potrzebna).
Utwórzmy nowy folder, w którym będziemy trzymali nasze serwisy: services
. Tam plik o nazwie agenda.js
, w którym będzie nasz serwis. Przeniesiemy tam funkcjonalność z kontrolera z pliku home/home.js
.
Problem będziemy mieli ze zmienną startTime
, bo jest ona typu prymitywnego (lista list
i funkcja myResult
będzie działać) i nie będzie działać bindowanie. Rozwiązania są dwa.
Rozwiązanie 1 – zmienna opakowana w obiekt
Nie ma referencji? To opakujmy zmienną w obiekt o nazwie model
, aby mieć się do czego odwołać. Jeśli aplikacja będzie się rozrastała, będzie można tam dorzucić dodatkowe zmienne (ale obecnie jest to trochę brzydkie rozwiązanie).
W serwisie będzie to wyglądało tak:
1 2 3 |
this.model = { 'startTime': "" }; |
W kontrolerze będziemy musieli przypisać obiekt do $scope
:
1 |
$scope.model = Agenda.model; |
A w widoku odwołać się do konkretnej zmiennej:
1 |
<input type="text" class="form-control" ng-model="model.startTime"> |
W kontrolerze oczywiście musimy przypisać wszystkie zmienne i funkcje do $scope
, aby mieć do nich dostęp w widoku:
1 2 3 |
$scope.model = Agenda.model; $scope.list = Agenda.list; $scope.myResult = Agenda.myResult; |
Rozwiązanie 2 – przypisanie całego serwisu do $scope
Jest jeszcze drugie rozwiązanie tego problemu. Ogólnie dobra praktyką w AngularJS przy bindowanie jest „zawsze używaj kropek” (bo oznacza to, że będzie referencja). Dlatego opakowaliśmy zmienną startTime
w obiekt model
, aby móc się odwołać do niego za pomocą kropki: model.startTime
.
Jeśli jednak nie opakujemy zmiennej, to skąd wziąć tę kropkę? Przypiszmy cały serwis do $scope
. Wtedy będziemy mieli: naszSerwis.startTime
.
Czyli w serwisie mamy ładnie zmienną:
1 |
this.startTime = ""; |
A w kontrolerze przypisujemy cały serwis do $scope
:
1 |
$scope.agenda = Agenda; |
Dzięki temu nie będziemy musieli tez pojedynczo przypisywać wszystkich zmiennych i funkcji z serwisu.
Jednak teraz w widoku musimy się odwołać do serwisu, aby uzyskać dostęp do danej zmiennej:
1 |
<input type="text" class="form-control" ng-model="agenda.startTime"> |
Na pierwszy rzut oka to wygląda gorzej, ale jeśli się zastanowić, to jest lepiej, bo dane są usystematyzowane (godzina rozpoczęcia wydarzenia powinna być w „pojemniku” związanym z agendą). Jeśli mielibyśmy kilka serwisów, byłoby to dużo bardziej czytelne niż poprzednie rozwiązanie (i mniej pracy oraz błędów z przepisywaniem wszystkich zmiennych – nie było też konfliktów nazw).
Kod serwisu (plik agenda.js
) wygląda następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
app.service('Agenda', function () { this.list = []; this.list.push({title: "", name: "", surname: "", time: ""}); this.startTime = ""; this.myResult = function () { var result = []; if(this.startTime !== "") { var lastTime = this.startTime; for (var i = 0; i < this.list.length; i++) { if(this.list[i].title !== "" && this.list[i].time !== "" && this.list[i].time !== undefined){ var endTime = addTime(lastTime, this.list[i].time); var concat = lastTime + "–" + endTime + " „" + this.list[i].title + "” – " + this.list[i].name + " " + this.list[i].surname; result.push(concat); lastTime = endTime; } } } return result; }; function addTime(start, time) { var now = new Date(); now.setHours(start.split(":")[0]); now.setMinutes(start.split(":")[1]); now.setHours(now.getHours()+parseInt(time.split(":")[0])); now.setMinutes(now.getMinutes()+parseInt(time.split(":")[1])); var hours = (now.getHours()<10?'0':'') + now.getHours(); var minutes = (now.getMinutes()<10?'0':'') + now.getMinutes(); return hours + ":" + minutes; }; }); |
Kontroler (plik home.js
) wygląda teraz tak:
1 2 3 4 5 6 7 |
.controller('homeController', function($scope, Agenda) { $scope.agenda = Agenda; $scope.addEmptyItem = function(){ Agenda.list.push({title: "", name: "", surname: ""}); }; }); |
Jak widzimy pozostała tu funkcja dodawania kolejnego pustego pola na liście. Nie zaśmiecaliśmy nią serwisu, bo nie dotyczy bezpośrednio przetwarzanych danych, a tylko widoku w danej zakładce. Dlatego idealnym miejscem na nią jest ten kontroler.
Zaś w widoku musimy odwoływać się do zmiennych za pomocą agenda.nazwaDanePola
:
1 2 3 4 5 6 |
... <input type="text" class="form-control" ng-model="agenda.startTime"> ... <tr ng-repeat="item in agenda.list" ng-click="$last && addEmptyItem()"> ... <span ng-repeat="item in agenda.myResult() track by $index">{{item}}<br /></span> |
Teraz nasze dane po wpisaniu będą zapamiętywane w serwisie i po przełączeniu się na inną zakładkę nie będą tracone.
Dodatkowa zakładka z wynikiem agendy
Możemy teraz bardzo łatwo skorzystać z już utworzonego serwisu. Dodajmy zakładkę, w której będzie umieszczony wynik agendy w formie tekstowej (czyli to co już mamy, ale na osobnej zakładce).
Tworzymy nowy folder (zgodnie ze strukturą „folder dla funkcjonalności/podstrony”) result
.
Umieszczamy w nim plik result.js
z kontrolerem i konfiguracją routingu dla danej zakładki:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
angular.module('agendaEditor') .config(function($routeProvider) { $routeProvider .when('/result', { templateUrl: 'result/result.html', controller: 'resultController' }); }) .controller('resultController', function($scope, Agenda) { $scope.agenda = Agenda; }); |
Dodajemy też plik result.html
z widokiem:
1 2 3 4 5 6 7 8 |
<div class="container"> <h3>Agenda</h3> <div class="panel panel-default"> <div class="panel-body"> <span ng-repeat="item in agenda.myResult() track by $index">{{item}}<br /></span> </div> </div> </div> |
Oczywiście musimy dodać w pliku index.html
plik z kontrolerem i zakładkę w menu:
1 2 3 4 5 |
... <script src="result/result.js"></script> ... <li><a href="#result">Result</a></li> ... |
W ten sposób szybko utworzyliśmy nową zakładkę z funkcjonalnością korzystającą z naszego serwisu.
Pingback: Prosty import i eksport danych w AgendaEditor | Marcin Kowalczyk – Blog IT
Pingback: Podsumowanie projektu AgendaEditor 2016 – Marcin Kowalczyk – Blog IT