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