Samouczek krok po kroku dla Twojej pierwszej aplikacji AngularJS
Opublikowany: 2022-03-11Co to jest AngularJS?
AngularJS to framework JavaScript MVC opracowany przez Google, który umożliwia tworzenie dobrze ustrukturyzowanych, łatwych do testowania i konserwacji aplikacji front-end.
I dlaczego powinienem go używać?
Jeśli nie próbowałeś jeszcze AngularJS, tracisz. Struktura składa się ze ściśle zintegrowanego zestawu narzędzi, który pomoże Ci zbudować dobrze ustrukturyzowane, bogate aplikacje po stronie klienta w sposób modułowy — z mniejszą ilością kodu i większą elastycznością.
AngularJS rozszerza HTML, dostarczając dyrektywy, które dodają funkcjonalność do znaczników i umożliwiają tworzenie potężnych szablonów dynamicznych. Możesz także tworzyć własne dyrektywy, tworząc komponenty wielokrotnego użytku, które spełniają Twoje potrzeby i odrzucając całą logikę manipulacji DOM.
Implementuje również dwukierunkowe wiązanie danych, bezproblemowo łącząc Twój HTML (widoki) z obiektami JavaScript (modelami). Mówiąc prościej, oznacza to, że każda aktualizacja Twojego modelu zostanie natychmiast odzwierciedlona w Twoim widoku bez potrzeby manipulacji DOM lub obsługi zdarzeń (np. za pomocą jQuery).
Wreszcie uwielbiam Angulara ze względu na jego elastyczność w komunikacji z serwerami. Podobnie jak większość struktur JavaScript MVC, umożliwia pracę z dowolną technologią po stronie serwera, o ile może obsługiwać Twoją aplikację za pośrednictwem internetowego interfejsu API RESTful. Ale Angular zapewnia również usługi oparte na XHR, które znacznie upraszczają Twój kod i pozwalają na przekształcenie wywołań API w usługi wielokrotnego użytku. W rezultacie możesz przenieść swój model i logikę biznesową do frontonu i tworzyć aplikacje internetowe niezależne od zaplecza. W tym poście zrobimy właśnie to, krok po kroku.
Więc od czego mam zacząć?
Najpierw zdecydujmy, jaką aplikację chcemy zbudować. W tym przewodniku wolelibyśmy nie spędzać zbyt dużo czasu na zapleczu, więc napiszemy coś w oparciu o dane, które są łatwo dostępne w Internecie — na przykład w aplikacji do obsługi kanałów sportowych!
Ponieważ jestem wielkim fanem wyścigów samochodowych i Formuły 1, użyję usługi API autosport jako naszego zaplecza. Na szczęście chłopaki z Ergast są na tyle uprzejmi, że udostępniają darmowe API do sportów motorowych, które będzie dla nas idealne.
Aby zobaczyć, co zamierzamy zbudować, spójrz na demo na żywo. Aby upiększyć demo i pochwalić się szablonami Angular, zastosowałem motyw Bootstrap z WrapBootstrap, ale ponieważ ten artykuł nie dotyczy CSS, po prostu oddzielę go od przykładów i pominę.
Wprowadzenie do samouczka
Zacznijmy naszą przykładową aplikację od jakiegoś boilerplate’u. Polecam projekt angular-seed, ponieważ zapewnia on nie tylko świetny szkielet do ładowania początkowego, ale także przygotowuje grunt pod testy jednostkowe z Karmą i Jasmine (nie będziemy przeprowadzać żadnych testów w tym demo, więc po prostu na razie odłóż to na bok; zobacz część 2 tego samouczka, aby uzyskać więcej informacji na temat konfigurowania projektu do testowania jednostkowego i kompleksowego).
EDYCJA (maj 2014): Odkąd napisałem ten samouczek, projekt angular-seed przeszedł kilka poważnych zmian (w tym dodatek Bowera jako menedżera pakietów). Jeśli masz wątpliwości, jak wdrożyć projekt, zajrzyj do pierwszej części ich przewodnika referencyjnego. W części 2 tego samouczka, między innymi Bower, jest omówiony bardziej szczegółowo.
OK, teraz, gdy sklonowaliśmy repozytorium i zainstalowaliśmy zależności, szkielet naszej aplikacji będzie wyglądał tak:
Teraz możemy zacząć kodować. Ponieważ próbujemy stworzyć kanał sportowy dla mistrzostw wyścigowych, zacznijmy od najbardziej odpowiedniego widoku: tabeli mistrzostw .
Biorąc pod uwagę, że mamy już zdefiniowaną listę sterowników w naszym zakresie (poczekaj ze mną – dostaniemy się tam) i ignorując każdy CSS (dla czytelności), nasz HTML może wyglądać tak:
<body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> </body>
Pierwszą rzeczą, jaką zauważysz w tym szablonie, jest użycie wyrażeń („{{” i „}}”) do zwracania wartości zmiennych. W rozwoju AngularJS wyrażenia pozwalają na wykonanie pewnych obliczeń w celu zwrócenia pożądanej wartości. Niektóre poprawne wyrażenia to:
-
{{ 1 + 1 }}
-
{{ 946757880 | date }}
-
{{ user.name }}
W efekcie wyrażenia są fragmentami kodu podobnymi do JavaScriptu. Ale pomimo tego, że jest bardzo potężny, nie powinieneś używać wyrażeń do implementacji logiki wyższego poziomu. W tym celu używamy dyrektyw.
Zrozumienie podstawowych dyrektyw
Drugą rzeczą, którą zauważysz, jest obecność ng-attributes
, których nie zobaczysz w typowych znacznikach. To są dyrektywy.
Na wysokim poziomie dyrektywy to znaczniki (takie jak atrybuty, znaczniki i nazwy klas), które informują AngularJS o dołączeniu danego zachowania do elementu DOM (lub przekształceniu go, zastąpieniu itp.). Rzućmy okiem na te, które już widzieliśmy:
Dyrektywa
ng-app
jest odpowiedzialna za ładowanie początkowe aplikacji, definiując jej zakres. W AngularJS możesz mieć wiele aplikacji na tej samej stronie, więc ta dyrektywa definiuje, gdzie każda odrębna aplikacja zaczyna się i kończy.Dyrektywa
ng-controller
określa, który kontroler będzie odpowiadał za Twój widok. W tym przypadku oznaczamydriversController
, który dostarczy naszą listę sterowników (driversList
).Dyrektywa
ng-repeat
jest jedną z najczęściej używanych i służy do definiowania zakresu szablonu podczas przeglądania kolekcji. W powyższym przykładzie replikuje wiersz w tabeli dla każdego sterownika zdriversList
.
Dodawanie kontrolerów
Oczywiście nasz widok nie ma sensu bez kontrolera. Dodajmy driversController
do naszego controllers.js:
angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope) { $scope.driversList = [ { Driver: { givenName: 'Sebastian', familyName: 'Vettel' }, points: 322, nationality: "German", Constructors: [ {name: "Red Bull"} ] }, { Driver: { givenName: 'Fernando', familyName: 'Alonso' }, points: 207, nationality: "Spanish", Constructors: [ {name: "Ferrari"} ] } ]; });
Być może zauważyłeś zmienną $scope
, którą przekazujemy jako parametr do kontrolera. Zmienna $scope
ma łączyć twój kontroler i widoki. W szczególności zawiera wszystkie dane, które będą używane w Twoim szablonie. Wszystko, co do niego dodasz (jak driversList
w powyższym przykładzie), będzie bezpośrednio dostępne w twoich widokach. Na razie po prostu popracujmy z fikcyjną (statyczną) tablicą danych, którą później zastąpimy naszą usługą API.
Teraz dodaj to do app.js:
angular.module('F1FeederApp', [ 'F1FeederApp.controllers' ]);
Za pomocą tego wiersza kodu faktycznie inicjujemy naszą aplikację i rejestrujemy moduły, od których jest zależna. Wrócimy do tego pliku ( app.js
) później.
Teraz zbierzmy wszystko razem w index.html
:
<!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> </head> <body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> <script src="bower_components/angular/angular.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="js/app.js"></script> <script src="js/services.js"></script> <script src="js/controllers.js"></script> </body> </html>
Modulo drobne błędy, możesz teraz uruchomić aplikację i sprawdzić (statyczną) listę sterowników.
Uwaga: jeśli potrzebujesz pomocy w debugowaniu aplikacji i wizualizacji modeli i zakresu w przeglądarce, polecam przyjrzeć się niesamowitej wtyczce Batarang dla przeglądarki Chrome.
Ładowanie danych z serwera
Ponieważ wiemy już, jak wyświetlić dane naszego kontrolera w naszym widoku, nadszedł czas, aby faktycznie pobrać dane na żywo z serwera RESTful.
Aby ułatwić komunikację z serwerami HTTP, AngularJS udostępnia usługi $http
i $resource
. Pierwsza jest tylko warstwą nad XMLHttpRequest lub JSONP, podczas gdy druga zapewnia wyższy poziom abstrakcji. Użyjemy $http
.
Aby wyodrębnić wywołania naszego serwera API z kontrolera, stwórzmy własną niestandardową usługę, która będzie pobierać nasze dane i działać jako opakowanie wokół $http
, dodając to do naszego services.js
:
angular.module('F1FeederApp.services', []). factory('ergastAPIservice', function($http) { var ergastAPI = {}; ergastAPI.getDrivers = function() { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK' }); } return ergastAPI; });
W pierwszych dwóch wierszach tworzymy nowy moduł ( F1FeederApp.services
) i rejestrujemy usługę w ramach tego modułu ( ergastAPIservice
). Zauważ, że do tej usługi przekazujemy $http
jako parametr. To informuje silnik wstrzykiwania zależności Angulara, że nasza nowa usługa wymaga (lub zależy od ) usługi $http
.
W podobny sposób musimy powiedzieć Angularowi, aby dołączył nasz nowy moduł do naszej aplikacji. Zarejestrujmy go za pomocą app.js
, zastępując nasz dotychczasowy kod:
angular.module('F1FeederApp', [ 'F1FeederApp.controllers', 'F1FeederApp.services' ]);
Teraz wszystko, co musimy zrobić, to trochę poprawić nasz plik controller.js
, dołączyć ergastAPIservice
jako zależność i będziemy mogli jechać:
angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; ergastAPIservice.getDrivers().success(function (response) { //Dig into the responde to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); });
Teraz przeładuj aplikację i sprawdź wynik. Zauważ, że nie dokonaliśmy żadnych zmian w naszym szablonie, ale dodaliśmy zmienną nameFilter
do naszego zakresu. Użyjmy tej zmiennej.

Filtry
Świetnie! Mamy funkcjonalny kontroler. Ale pokazuje tylko listę sterowników. Dodajmy trochę funkcjonalności, wprowadzając proste wyszukiwanie tekstowe, które będzie filtrować naszą listę. Dodajmy następujący wiersz do naszego index.html
, tuż pod tagiem <body>
:
<input type="text" ng-model="nameFilter" placeholder="Search..."/>
Korzystamy teraz z dyrektywy ng-model
. Ta dyrektywa wiąże nasze pole tekstowe ze zmienną $scope.nameFilter
i zapewnia, że jej wartość jest zawsze aktualna z wartością wejściową. Teraz wejdźmy jeszcze raz na index.html i dokonajmy małej korekty w wierszu zawierającym dyrektywę ng-repeat
:
<tr ng-repeat="driver in driversList | filter: nameFilter">
Ta linia mówi ng-repeat
, że przed wyprowadzeniem danych tablica driversList
musi zostać przefiltrowana według wartości przechowywanej w nameFilter
.
W tym momencie rozpoczyna się dwukierunkowe wiązanie danych: za każdym razem, gdy wartość jest wprowadzana w polu wyszukiwania, Angular natychmiast zapewnia, że $scope.nameFilter
, który z nim skojarzyliśmy, zostanie zaktualizowany o nową wartość. Ponieważ wiązanie działa w obie strony, w momencie aktualizacji wartości nameFilter
, druga dyrektywa z nim związana (tj. ng-repeat
) również otrzymuje nową wartość, a widok jest natychmiast aktualizowany.
Odśwież aplikację i sprawdź pasek wyszukiwania.
Zauważ, że ten filtr będzie szukał słowa kluczowego we wszystkich atrybutach modelu, łącznie z tymi, których nie używałeś. Załóżmy, że chcemy filtrować tylko według Driver.givenName
i Driver.familyName
: Najpierw dodajemy do driversController
, tuż pod $scope.driversList = [];
linia:
$scope.searchFilter = function (driver) { var keyword = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || keyword.test(driver.Driver.givenName) || keyword.test(driver.Driver.familyName); };
Teraz wracając do index.html
, aktualizujemy wiersz zawierający dyrektywę ng-repeat
:
<tr ng-repeat="driver in driversList | filter: searchFilter">
Odśwież aplikację jeszcze raz, a teraz mamy wyszukiwanie według nazwy.
Trasy
Naszym kolejnym celem jest stworzenie strony z danymi kierowcy, która pozwoli nam kliknąć każdego kierowcę i zobaczyć szczegóły jego kariery.
Najpierw uwzględnijmy usługę $routeProvider
(w app.js
), która pomoże nam poradzić sobie z tymi zróżnicowanymi trasami aplikacji . Następnie dodamy dwie takie trasy: jedną dla tabeli mistrzostw, a drugą dla danych kierowcy. Oto nasz nowy app.js
:
angular.module('F1FeederApp', [ 'F1FeederApp.services', 'F1FeederApp.controllers', 'ngRoute' ]). config(['$routeProvider', function($routeProvider) { $routeProvider. when("/drivers", {templateUrl: "partials/drivers.html", controller: "driversController"}). when("/drivers/:id", {templateUrl: "partials/driver.html", controller: "driverController"}). otherwise({redirectTo: '/drivers'}); }]);
Po tej zmianie przejście do strony http://domain/#/drivers
spowoduje załadowanie sterownika driversController
i wyszukanie częściowego widoku do wyrenderowania w części partials/drivers.html
. Ale poczekaj! Nie mamy jeszcze żadnych częściowych widoków, prawda? Te też będziemy musieli stworzyć.
Widoki częściowe
AngularJS pozwoli Ci powiązać trasy z określonymi kontrolerami i widokami.
Ale najpierw musimy powiedzieć Angularowi, gdzie renderować te częściowe widoki. W tym celu użyjemy dyrektywy ng-view
, modyfikując nasz index.html
tak, aby odzwierciedlał następujące elementy:
<!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> </head> <body ng-app="F1FeederApp"> <ng-view></ng-view> <script src="bower_components/angular/angular.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="js/app.js"></script> <script src="js/services.js"></script> <script src="js/controllers.js"></script> </body> </html>
Teraz za każdym razem, gdy nawigujemy po naszych trasach aplikacji, Angular załaduje powiązany widok i wyrenderuje go w miejscu tagu <ng-view>
. Wszystko, co musimy zrobić, to utworzyć plik o nazwie partials/drivers.html
i umieścić tam nasz kod HTML tabeli mistrzostw. Wykorzystamy również tę szansę, aby połączyć nazwisko kierowcy z naszą trasą zawierającą dane kierowcy:
<input type="text" ng-model="nameFilter" placeholder="Search..."/> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList | filter: searchFilter"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> <a href="#/drivers/{{driver.Driver.driverId}}"> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </a> </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table>
Na koniec zdecydujmy, co chcemy pokazać na stronie szczegółów. Co powiesz na podsumowanie wszystkich istotnych faktów dotyczących kierowcy (np. urodzenie, narodowość) wraz z tabelą zawierającą jego/jej ostatnie wyniki? W tym celu dodajemy do services.js
:
angular.module('F1FeederApp.services', []) .factory('ergastAPIservice', function($http) { var ergastAPI = {}; ergastAPI.getDrivers = function() { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverDetails = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverRaces = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/results.json?callback=JSON_CALLBACK' }); } return ergastAPI; });
Tym razem przekazujemy do usługi identyfikator kierowcy, dzięki czemu uzyskujemy informacje dotyczące wyłącznie konkretnego kierowcy. Teraz modyfikujemy controllers.js
:
angular.module('F1FeederApp.controllers', []). /* Drivers controller */ controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; $scope.searchFilter = function (driver) { var re = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || re.test(driver.Driver.givenName) || re.test(driver.Driver.familyName); }; ergastAPIservice.getDrivers().success(function (response) { //Digging into the response to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); }). /* Driver controller */ controller('driverController', function($scope, $routeParams, ergastAPIservice) { $scope.id = $routeParams.id; $scope.races = []; $scope.driver = null; ergastAPIservice.getDriverDetails($scope.id).success(function (response) { $scope.driver = response.MRData.StandingsTable.StandingsLists[0].DriverStandings[0]; }); ergastAPIservice.getDriverRaces($scope.id).success(function (response) { $scope.races = response.MRData.RaceTable.Races; }); });
Ważną rzeczą do zauważenia jest to, że właśnie wstrzyknęliśmy usługę $routeParams
do kontrolera sterownika. Ta usługa umożliwi nam dostęp do naszych parametrów URL (w tym przypadku dla :id
) za pomocą $routeParams.id
.
Teraz, gdy mamy nasze dane w zakresie, potrzebujemy tylko pozostałego widoku częściowego. Stwórzmy plik o nazwie partials/driver.html
i dodajmy:
<section> <a href="./#/drivers"><- Back to drivers list</a> <nav class="main-nav"> <div class="driver-picture"> <div class="avatar"> <img ng-show="driver" src="img/drivers/{{driver.Driver.driverId}}.png" /> <img ng-show="driver" src="img/flags/{{driver.Driver.nationality}}.png" /><br/> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </div> </div> <div class="driver-status"> Country: {{driver.Driver.nationality}} <br/> Team: {{driver.Constructors[0].name}}<br/> Birth: {{driver.Driver.dateOfBirth}}<br/> <a href="{{driver.Driver.url}}" target="_blank">Biography</a> </div> </nav> <div class="main-content"> <table class="result-table"> <thead> <tr><th colspan="5">Formula 1 2013 Results</th></tr> </thead> <tbody> <tr> <td>Round</td> <td>Grand Prix</td> <td>Team</td> <td>Grid</td> <td>Race</td> </tr> <tr ng-repeat="race in races"> <td>{{race.round}}</td> <td><img src="img/flags/{{race.Circuit.Location.country}}.png" />{{race.raceName}}</td> <td>{{race.Results[0].Constructor.name}}</td> <td>{{race.Results[0].grid}}</td> <td>{{race.Results[0].position}}</td> </tr> </tbody> </table> </div> </section>
Zauważ, że teraz robimy dobry użytek z dyrektywy ng-show
. Ta dyrektywa pokaże element HTML tylko wtedy, gdy podane wyrażenie jest true
(tj. ani false
, ani null
). W takim przypadku awatar pojawi się dopiero po załadowaniu obiektu sterownika do zakresu przez kontroler.
Ostatnie poprawki
Dodaj kilka CSS i wyrenderuj swoją stronę. Powinieneś otrzymać coś takiego:
Możesz teraz uruchomić aplikację i upewnić się, że obie trasy działają zgodnie z oczekiwaniami. Można również dodać menu statyczne do pliku index.html
, aby poprawić możliwości nawigacji użytkownika. Możliwości są nieskończone.
EDYCJA (maj 2014): Otrzymałem wiele próśb o wersję kodu do pobrania, który tworzymy w tym samouczku. Dlatego zdecydowałem się opublikować go tutaj (pozbawiony jakiegokolwiek CSS). Jednak naprawdę nie polecam jej pobierania, ponieważ ten przewodnik zawiera każdy krok, który musisz wykonać, aby zbudować tę samą aplikację własnymi rękami, co będzie znacznie bardziej użytecznym i efektywnym ćwiczeniem do nauki.
Wniosek
W tym momencie samouczka omówiliśmy wszystko, czego potrzebujesz do napisania prostej aplikacji (takiej jak moduł dostarczania danych Formuły 1). Każda z pozostałych stron w demo na żywo (np. tabela mistrzostw konstruktorów, szczegóły zespołu, kalendarz) ma tę samą podstawową strukturę i koncepcje, które omówiliśmy tutaj.
Na koniec pamiętaj, że Angular to bardzo potężna platforma i ledwie zarysowaliśmy wszystko, co ma do zaoferowania. W części 2 tego samouczka podamy przykłady, dlaczego Angular wyróżnia się na tle swoich peer-endowych frameworków MVC: testowalność. Przyjrzymy się procesowi pisania i uruchamiania testów jednostkowych za pomocą Karmy, osiągania ciągłej integracji z Yeomen, Grunt i Bower oraz innych mocnych stron tego fantastycznego front-endu.