Ein Schritt-für-Schritt-Tutorial für Ihre erste AngularJS-App
Veröffentlicht: 2022-03-11Was ist AngularJS?
AngularJS ist ein von Google entwickeltes JavaScript-MVC-Framework, mit dem Sie gut strukturierte, leicht testbare und wartbare Front-End-Anwendungen erstellen können.
Und warum sollte ich es verwenden?
Wenn Sie AngularJS noch nicht ausprobiert haben, verpassen Sie etwas. Das Framework besteht aus einem eng integrierten Toolset, mit dem Sie gut strukturierte, umfangreiche clientseitige Anwendungen auf modulare Weise erstellen können – mit weniger Code und mehr Flexibilität.
AngularJS erweitert HTML, indem es Anweisungen bereitstellt, die Ihrem Markup Funktionalität hinzufügen und es Ihnen ermöglichen, leistungsstarke dynamische Vorlagen zu erstellen. Sie können auch Ihre eigenen Anweisungen erstellen, wiederverwendbare Komponenten erstellen, die Ihre Anforderungen erfüllen, und die gesamte DOM-Manipulationslogik abstrahieren.
Es implementiert auch eine bidirektionale Datenbindung und verbindet Ihr HTML (Ansichten) nahtlos mit Ihren JavaScript-Objekten (Modellen). Einfach ausgedrückt bedeutet dies, dass jede Aktualisierung Ihres Modells sofort in Ihrer Ansicht widergespiegelt wird, ohne dass eine DOM-Manipulation oder Ereignisbehandlung (z. B. mit jQuery) erforderlich ist.
Schließlich liebe ich Angular wegen seiner Flexibilität in Bezug auf die Serverkommunikation. Wie die meisten JavaScript-MVC-Frameworks können Sie mit jeder serverseitigen Technologie arbeiten, solange sie Ihre App über eine RESTful-Web-API bereitstellen kann. Aber Angular bietet zusätzlich zu XHR auch Dienste, die Ihren Code drastisch vereinfachen und es Ihnen ermöglichen, API-Aufrufe in wiederverwendbare Dienste zu abstrahieren. Infolgedessen können Sie Ihr Modell und Ihre Geschäftslogik auf das Front-End verschieben und Back-End-agnostische Web-Apps erstellen. In diesem Beitrag werden wir genau das tun, Schritt für Schritt.
Also, wo fange ich an?
Lassen Sie uns zunächst die Art der App festlegen, die wir erstellen möchten. In diesem Leitfaden möchten wir nicht zu viel Zeit mit dem Back-End verbringen, also schreiben wir etwas, das auf Daten basiert, die im Internet leicht zugänglich sind – wie eine Sport-Feed-App!
Da ich ein großer Fan des Motorsports und der Formel 1 bin, werde ich einen Autosport-API-Dienst als unser Back-End verwenden. Glücklicherweise sind die Jungs von Ergast so freundlich, uns eine kostenlose Motorsport-API zur Verfügung zu stellen, die perfekt für uns ist.
Werfen Sie einen Blick auf die Live-Demo, um einen kleinen Vorgeschmack auf das zu bekommen, was wir bauen werden. Um die Demo zu verschönern und etwas Angular-Templating zu zeigen, habe ich ein Bootstrap-Design von WrapBootstrap angewendet, aber da es in diesem Artikel nicht um CSS geht, werde ich es einfach von den Beispielen abstrahieren und weglassen.
Erste-Schritte-Tutorial
Beginnen wir unsere Beispiel-App mit einigen Boilerplates. Ich empfehle das Angle-Seed-Projekt, da es Ihnen nicht nur ein großartiges Skelett für das Bootstrapping bietet, sondern auch den Grundstein für Unit-Tests mit Karma und Jasmine legt (wir werden in dieser Demo keine Tests durchführen, also werden wir nur lassen Sie dieses Zeug vorerst beiseite; siehe Teil 2 dieses Tutorials für weitere Informationen zum Einrichten Ihres Projekts für Unit- und End-to-End-Tests).
BEARBEITEN (Mai 2014): Seit ich dieses Tutorial geschrieben habe, hat das Angle-Seed-Projekt einige starke Änderungen erfahren (einschließlich der Hinzufügung von Bower als Paketmanager). Wenn Sie Zweifel haben, wie das Projekt bereitgestellt werden soll, werfen Sie einen kurzen Blick auf den ersten Abschnitt ihres Referenzhandbuchs. In Teil 2 dieses Tutorials wird unter anderem Bower ausführlicher behandelt.
OK, jetzt, da wir das Repository geklont und die Abhängigkeiten installiert haben, sieht das Skelett unserer App so aus:
Jetzt können wir mit dem Codieren beginnen. Da wir versuchen, einen Sport-Feed für eine Rennmeisterschaft zu erstellen, beginnen wir mit der relevantesten Ansicht: der Meisterschaftstabelle .
Da wir bereits eine Treiberliste in unserem Bereich definiert haben (hängen Sie mit mir - wir werden es schaffen) und jegliches CSS ignorieren (aus Gründen der Lesbarkeit), könnte unser HTML so aussehen:
<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>
Das Erste, was Ihnen in dieser Vorlage auffallen wird, ist die Verwendung von Ausdrücken („{{“ und „}}“), um Variablenwerte zurückzugeben. In der AngularJS-Entwicklung können Sie mit Ausdrücken einige Berechnungen ausführen, um einen gewünschten Wert zurückzugeben. Einige gültige Ausdrücke wären:
-
{{ 1 + 1 }}
-
{{ 946757880 | date }}
-
{{ user.name }}
Genau genommen sind Ausdrücke JavaScript-ähnliche Snippets. Aber obwohl sie sehr leistungsfähig sind, sollten Sie keine Ausdrücke verwenden, um eine Logik auf höherer Ebene zu implementieren. Dafür verwenden wir Direktiven.
Grundlegende Direktiven verstehen
Das zweite, was Ihnen auffallen wird, ist das Vorhandensein von ng-attributes
, die Sie in einem typischen Markup nicht sehen würden. Das sind Richtlinien.
Auf hoher Ebene sind Direktiven Markierungen (wie Attribute, Tags und Klassennamen), die AngularJS anweisen, ein bestimmtes Verhalten an ein DOM-Element anzuhängen (oder es zu transformieren, zu ersetzen usw.). Werfen wir einen Blick auf die, die wir bereits gesehen haben:
Die
ng-app
Direktive ist für das Bootstrapping Ihrer App verantwortlich, die ihren Umfang definiert. In AngularJS können Sie mehrere Apps auf derselben Seite haben, daher definiert diese Direktive, wo jede einzelne App beginnt und endet.Die Direktive
ng-controller
definiert, welcher Controller für Ihre Ansicht zuständig ist. In diesem Fall bezeichnen wir dendriversController
, der unsere Liste der Treiber (driversList
) bereitstellt.Die Direktive
ng-repeat
ist eine der am häufigsten verwendeten und dient dazu, Ihren Vorlagenbereich beim Durchlaufen von Sammlungen zu definieren. Im obigen Beispiel repliziert es eine Zeile in der Tabelle für jeden Fahrer indriversList
.
Hinzufügen von Controllern
Natürlich nützt unsere Ansicht ohne Controller nichts. Lassen Sie uns zu unserer controllers.js " driversController
" hinzufügen:
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"} ] } ]; });
Sie haben vielleicht die $scope
Variable bemerkt, die wir als Parameter an den Controller übergeben. Die Variable $scope
soll Ihren Controller und Ihre Ansichten verknüpfen. Insbesondere enthält es alle Daten, die in Ihrer Vorlage verwendet werden. Alles, was Sie hinzufügen (wie die driversList
im obigen Beispiel), ist in Ihren Ansichten direkt zugänglich. Lassen Sie uns zunächst nur mit einem (statischen) Dummy-Datenarray arbeiten, das wir später durch unseren API-Dienst ersetzen werden.
Fügen Sie dies nun zu app.js hinzu:
angular.module('F1FeederApp', [ 'F1FeederApp.controllers' ]);
Mit dieser Codezeile initialisieren wir unsere App tatsächlich und registrieren die Module, von denen sie abhängt. Wir werden später auf diese Datei ( app.js
) zurückkommen.
Nun fügen wir alles in index.html
zusammen:
<!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 kleinere Fehler, Sie können jetzt Ihre App starten und Ihre (statische) Treiberliste überprüfen.
Hinweis: Wenn Sie Hilfe beim Debuggen Ihrer App und beim Visualisieren Ihrer Modelle und Bereiche im Browser benötigen, empfehle ich Ihnen, sich das fantastische Batarang-Plug-in für Chrome anzusehen.
Laden von Daten vom Server
Da wir bereits wissen, wie die Daten unseres Controllers in unserer Ansicht angezeigt werden, ist es an der Zeit, tatsächlich Live-Daten von einem RESTful-Server abzurufen.
Um die Kommunikation mit HTTP-Servern zu erleichtern, stellt AngularJS die Dienste $http
und $resource
bereit. Ersteres ist nur eine Schicht über XMLHttpRequest oder JSONP, während letzteres eine höhere Abstraktionsebene bietet. Wir verwenden $http
.
Um unsere Server-API-Aufrufe vom Controller zu abstrahieren, erstellen wir unseren eigenen benutzerdefinierten Dienst, der unsere Daten abruft und als Wrapper um $http
fungiert, indem wir dies zu unserer services.js
hinzufügen:
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; });
Mit den ersten beiden Zeilen erstellen wir ein neues Modul ( F1FeederApp.services
) und registrieren einen Dienst innerhalb dieses Moduls ( ergastAPIservice
). Beachten Sie, dass wir $http
als Parameter an diesen Dienst übergeben. Dies teilt der Dependency-Injection-Engine von Angular mit, dass unser neuer Dienst den $http
-Dienst benötigt (oder davon abhängt ).
Auf ähnliche Weise müssen wir Angular anweisen, unser neues Modul in unsere App aufzunehmen. Lassen Sie uns es mit app.js
registrieren und unseren vorhandenen Code ersetzen durch:
angular.module('F1FeederApp', [ 'F1FeederApp.controllers', 'F1FeederApp.services' ]);
Jetzt müssen wir nur noch unsere controller.js
ein wenig optimieren, ergastAPIservice
als Abhängigkeit hinzufügen, und wir können loslegen:
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; }); });
Laden Sie nun die App neu und sehen Sie sich das Ergebnis an. Beachten Sie, dass wir keine Änderungen an unserer Vorlage vorgenommen haben, aber unserem Gültigkeitsbereich eine nameFilter
Variable hinzugefügt haben. Lassen Sie uns diese Variable verwenden.

Filter
Toll! Wir haben einen funktionierenden Controller. Aber es zeigt nur eine Liste von Treibern. Fügen wir einige Funktionen hinzu, indem wir eine einfache Textsucheingabe implementieren, die unsere Liste filtert. Fügen wir unserer index.html
direkt unter dem <body>
-Tag die folgende Zeile hinzu:
<input type="text" ng-model="nameFilter" placeholder="Search..."/>
Wir verwenden jetzt die ng-model
Direktive. Diese Direktive bindet unser Textfeld an die Variable $scope.nameFilter
und stellt sicher, dass ihr Wert immer aktuell mit dem Eingabewert ist. Besuchen wir nun noch einmal index.html und nehmen eine kleine Anpassung an der Zeile vor, die die ng-repeat
Direktive enthält:
<tr ng-repeat="driver in driversList | filter: nameFilter">
Diese Zeile teilt ng-repeat
mit, dass vor der Ausgabe der Daten das Array driversList
nach dem in nameFilter
gespeicherten Wert gefiltert werden muss.
An diesem Punkt setzt die bidirektionale Datenbindung ein: Jedes Mal, wenn ein Wert in das Suchfeld eingegeben wird, sorgt Angular sofort dafür, dass der $scope.nameFilter
, den wir ihm zugeordnet haben, mit dem neuen Wert aktualisiert wird. Da die Bindung in beide Richtungen funktioniert, erhält in dem Moment, in dem der nameFilter
-Wert aktualisiert wird, auch die zweite damit verbundene Direktive (dh ng-repeat
) den neuen Wert und die Ansicht wird sofort aktualisiert.
Laden Sie die App neu und sehen Sie sich die Suchleiste an.
Beachten Sie, dass dieser Filter in allen Attributen des Modells nach dem Schlüsselwort sucht, einschließlich derjenigen, die nicht verwendet wurden. Nehmen wir an, wir wollen nur nach Driver.givenName
und Driver.familyName
: Zuerst fügen wir zu driversController
, direkt unter $scope.driversList = [];
Linie:
$scope.searchFilter = function (driver) { var keyword = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || keyword.test(driver.Driver.givenName) || keyword.test(driver.Driver.familyName); };
Zurück zu index.html
aktualisieren wir nun die Zeile, die die Direktive ng-repeat
enthält:
<tr ng-repeat="driver in driversList | filter: searchFilter">
Laden Sie die App noch einmal neu und jetzt haben wir eine Suche nach Namen.
Routen
Unser nächstes Ziel ist es, eine Fahrerdetailseite zu erstellen, auf der wir auf jeden Fahrer klicken und seine Karrieredetails sehen können.
Lassen Sie uns zunächst den Dienst $routeProvider
(in app.js
) einbeziehen, der uns beim Umgang mit diesen unterschiedlichen Anwendungsrouten helfen wird. Dann fügen wir zwei solcher Routen hinzu: eine für die Meisterschaftstabelle und eine weitere für die Fahrerdetails. Hier ist unsere neue 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'}); }]);
Mit dieser Änderung lädt das Navigieren zu http://domain/#/drivers
den driversController
und sucht nach der partiellen Ansicht, die in partials/drivers.html
. Aber warte! Wir haben noch keine Teilansichten, richtig? Die müssen wir auch erstellen.
Teilansichten
Mit AngularJS können Sie Ihre Routen an bestimmte Controller und Ansichten binden.
Aber zuerst müssen wir Angular mitteilen, wo diese Teilansichten gerendert werden sollen. Dazu verwenden wir die ng-view
Direktive und ändern unsere index.html
so, dass sie Folgendes widerspiegelt:
<!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>
Wenn wir jetzt durch unsere App-Routen navigieren, lädt Angular die zugehörige Ansicht und rendert sie anstelle des <ng-view>
-Tags. Alles, was wir tun müssen, ist eine Datei namens partials/drivers.html
zu erstellen und unsere HTML-Meisterschaftstabelle dort abzulegen. Wir werden diese Gelegenheit auch nutzen, um den Fahrernamen mit unserer Route mit den Fahrerdetails zu verknüpfen:
<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>
Lassen Sie uns abschließend entscheiden, was auf der Detailseite angezeigt werden soll. Wie wäre es mit einer Zusammenfassung aller relevanten Fakten zum Fahrer (zB Geburt, Nationalität) und einer tabellarischen Darstellung seiner/ihrer aktuellen Ergebnisse? Dazu fügen wir services.js
hinzu:
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; });
Diesmal stellen wir dem Dienst die Fahrer-ID bereit, damit wir die Informationen abrufen, die nur für einen bestimmten Fahrer relevant sind. Jetzt ändern wir 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; }); });
Wichtig ist hier, dass wir gerade den $routeParams
-Dienst in den Treiber-Controller eingefügt haben. Dieser Dienst ermöglicht uns den Zugriff auf unsere URL-Parameter (in diesem Fall für die :id
) mithilfe $routeParams.id
.
Nachdem wir nun unsere Daten im Geltungsbereich haben, benötigen wir nur noch die verbleibende Teilansicht. Lassen Sie uns eine Datei namens partials/driver.html
und hinzufügen:
<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>
Beachten Sie, dass wir jetzt die Direktive ng-show
sinnvoll einsetzen. Diese Direktive zeigt das HTML-Element nur an, wenn der bereitgestellte Ausdruck true
ist (dh weder false
noch null
). In diesem Fall wird der Avatar erst angezeigt, nachdem das Fahrerobjekt vom Controller in den Bereich geladen wurde.
Feinschliff
Fügen Sie eine Reihe von CSS hinzu und rendern Sie Ihre Seite. Sie sollten mit so etwas enden:
Sie können jetzt Ihre App starten und sicherstellen, dass beide Routen wie gewünscht funktionieren. Sie könnten index.html
auch ein statisches Menü hinzufügen, um die Navigationsmöglichkeiten des Benutzers zu verbessern. Die Möglichkeiten sind endlos.
BEARBEITEN (Mai 2014): Ich habe viele Anfragen nach einer herunterladbaren Version des Codes erhalten, den wir in diesem Tutorial erstellen. Ich habe mich daher entschieden, es hier zu veröffentlichen (ohne jegliches CSS). Ich empfehle jedoch wirklich nicht , es herunterzuladen, da dieses Handbuch jeden einzelnen Schritt enthält, den Sie benötigen, um dieselbe Anwendung mit Ihren eigenen Händen zu erstellen, was eine viel nützlichere und effektivere Lernübung sein wird.
Fazit
An dieser Stelle des Tutorials haben wir alles behandelt, was Sie zum Schreiben einer einfachen App (wie eines Formel-1-Feeders) benötigen. Jede der verbleibenden Seiten in der Live-Demo (z. B. Konstrukteurs-Meisterschaftstabelle, Teamdetails, Kalender) hat die gleiche grundlegende Struktur und die gleichen Konzepte, die wir hier besprochen haben.
Denken Sie schließlich daran, dass Angular ein sehr leistungsfähiges Framework ist und wir in Bezug auf alles, was es zu bieten hat, kaum an der Oberfläche gekratzt haben. In Teil 2 dieses Tutorials geben wir Beispiele dafür, warum Angular sich von seinen Peer-Front-End-MVC-Frameworks abhebt: Testbarkeit. Wir werden den Prozess des Schreibens und Ausführens von Unit-Tests mit Karma, das Erreichen einer kontinuierlichen Integration mit Yeomen, Grunt und Bower sowie andere Stärken dieses fantastischen Front-End-Frameworks überprüfen.