
Angular Tutorial – Teil 2: Komponenten, Datenbindung und HTTP-Zugriff
Erste Komponente, die via HTTP Daten abruft und via Datenbindung darstellt.
Um Ihnen die einzelnen Aspekte von Angular zu vermitteln, verwenden wir in diesem Tutorial ein durchgängiges Beispiel. Sie können es in unserem GitHub-Account finden. Dabei handelt es sich um eine Anwendung zum Buchen von Flügen. Wir setzen dazu auf die im letzten Teil dieses Tutorials generierte Anwendung auf:
Interface für Datenobjekt erzeugen
Da wir mit Flügen arbeiten wollen, brauchen wir einen Datentyp der die Struktur der Flug-Objekte widerspiegelt. Hierzu legen wir zunächst im Ordner src/app
eine Datei flight.ts
mit dem folgenden Interface an:
// src/app/flight.ts
export interface Flight {
id: number;
from: string;
to: string;
date: string;
delayed?: boolean;
}
Angular-Komponente erzeugen
Nun erstellen wir eine Angular-Komponente für den besprochenen Anwendungsfall erstellen. Wechseln Sie dazu auf die Konsole. Führen Sie im Hauptverzeichnis der Anwendung (Verzeichnis mit der angular.json
) den folgenden Befehl aus:
ng generate component flight-search
Die Befehle der CLI lassen sich abkürzen, die betrachtete Anweisung könnte man beispielsweise auch wie folgt formulieren:
ng g c flight-search
Mit dem in Teil 1 erwähnten Visual Studio Plug-in Angular Schematics Angular Schematics lässt sich dieser CLI-Befehl auch direkt über Visual Studio Code anstoßen. Wählen Sie dazu die Anweisung Angular: Generate a component aus dem Kontextmenü des gewünschten Ordners.
Nach dem Auswählen dieser Anweisung stellt Ihnen Visual Studio Code mehrere Fragen. Die Frage nach dem Komponentennamen beantworten Sie analog zum oben diskutierten Befehl mit
flight-search
. Die anderen Fragen können Sie einfach mit Enter quittieren, um mit den Standardeinstellungen der CLI vorlieb zu nehmen.
Die Angular CLI generiert daraufhin mehrere Dateien für die gewünschte Komponente:
Diese Dateien richtet die CLI im Ordner src/app/flight-search
ein:
-
flight-search.component.html: Das Template der Komponente. Es bestimmt, wie Angular die Komponente darstellt.
-
flight-search.component.ts: Die TypeScript-Klasse, die die Komponente repräsentiert. Sie definiert das gewünschte Verhalten.
-
flight-search.component.scss: Die Stylesheet-Datei mit lokalen Styles für unsere Komponente.
Die Dateien flight-search.component.ts und flight-search.component.html* werden wir in den nachfolgenden Abschnitten näher betrachten und für unsere Zwecke anpassen.
Komponentenlogik
Die generierte Datei flight-search.component.ts
beinhaltet das Grundgerüst für unsere Komponentenlogik:
// src/app/flight-search/flight-search.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-flight-search',
templateUrl: './flight-search.component.html',
styleUrls: ['./flight-search.component.scss']
})
export class FlightSearchComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
Viele der hier generierten Konstrukte haben wir bereits in Teil 1 im Rahmen der AppComponent
besprochen. Allerdings möchten wir hier Ihre Aufmerksamkeit auf ein paar Details lenken:
-
Der Selektor lautet
app-flight-search
. Das Präfixapp
wurde von der CLI eingefügt. Diese Präfixe sollen Namenskonflikte mit Komponenten aus Bibliotheken verhindern. -
Die generierte Klasse nennt sich
FlightSearchComponent
, während die zugrunde liegende Datei den Namenflight-search.component.ts
erhalten hat. Hierbei handelt es sich um die üblichen Namenskonventionen in der Welt von Angular. -
FlightSearchComponent
implementiert das InterfaceOnInit
, das wiederum die MethodengOnInit
vorgibt. Diese Methode ruft Angular nach dem Initialisieren der Komponente auf, und somit kann sie für Initialisierungen von Eigenschaften verwendet werden.
Lassen Sie uns nun dieses Grundgerüst ein wenig ausbauen, um eine Suche nach Flügen zu ermöglichen:
// src/app/flight-search/flight-search.component.ts
import { Component, OnInit } from '@angular/core';
import { Flight } from '../flight';
@Component({
selector: 'app-flight-search',
templateUrl: './flight-search.component.html',
styleUrls: ['./flight-search.component.scss']
})
export class FlightSearchComponent implements OnInit {
from = 'Hamburg';
to = 'Graz';
flights: Array<Flight> = [];
selectedFlight: Flight | null = null;
constructor() {
}
ngOnInit(): void {
}
search(): void {
// Implementierung folgt weiter unten.
}
select(f: Flight): void {
this.selectedFlight = f;
}
}
Die Eigenschaften from
und to
repräsentieren die Suchkriterien für die gewünschten Flüge. Die Standardwerte sollen hier verhindern, dass wir später immer wieder die gleichen Suchkriterien eingeben müssen. Außerdem lassen sie uns auf den ersten Blick erkennen, ob der weiter unten angestrebte automatische Abgleich zwischen den Eigenschaften und den Textfeldern funktioniert.
Das Array flights
nimmt die gefundenen Flüge auf. Es ist mit dem zu erzeugten Interface Flight
typisiert.
Die Eigenschaft selectedFlight
repräsentiert den ausgewählten Flug. Damit sie initial den Wert null bekommen kann, ist sie vom Typ Flight | null
.
Angular verwendet standardmäßig TypeScript im Strict Mode. Das bedeutet unter anderem, dass Sie explizit angeben müssen, ob Eigenschaften den Wert
null
bzw.undefined
aufnehmen dürfen. In diesen Fällen zwingt Sie TypeScript auch dazu, vor der Verwendung gegen diese Werte zu prüfen.
Die Methode search
kümmert sich um das Abrufen der Flüge. Wir werden uns um ihre Implementierung gleich kümmern. Die Methode select
notiert sich den vom Benutzer ausgewählten Flug.
Werde Angular-Profi mit unseren Schulungen
Von der Angular Praxis Schulung für Einsteiger & Autodidakten über Advanced-Workshops für Fortgeschrittene bis hin zu Angular Deep Dives für erfahrene Anwender. In unseren Seminaren erfahren die Teilnehmer alles, was man als Entwickler für erfolgreiche Angular-Projekte braucht.
Auf das Backend zugreifen
Für Ihre Hauptaufgabe muss die FlightSearchComponent
via HTTP auf eine Web-API mit Flügen zugreifen. Für solche Vorhaben bietet Angular die Klasse HttpClient
. Da diese Klasse wiederverwendbare Dienste anbietet, ist auch von einem Service die Rede.
Um Zugriff auf den Service zu bekommen, müssen Sie zunächst das HttpClientModule
in Ihr AppModule
importieren:
// src/app/app.module.ts
[...]
// Diese Zeile einfügen:
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
//Diese Zeile unter *imports* einfügen:
HttpClientModule,
BrowserModule
],
declarations: [
[...]
],
providers: [],
bootstrap: [
AppComponent
]
})
export class AppModule { }
Danach können Sie über den Konstruktor der FlightSearchComponent
eine Instanz von HttpClient
anfordern:
// src/app/flight-search/flight-search.component.ts
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { Flight } from '../flight';
@Component({
selector: 'app-flight-search',
templateUrl: './flight-search.component.html',
styleUrls: ['./flight-search.component.scss']
})
export class FlightSearchComponent implements OnInit {
from = 'Hamburg';
to = 'Graz';
flights: Array<Flight> = [];
selectedFlight: Flight | null = null;
// HttpClient anfordern:
constructor(private http: HttpClient) {
}
[...]
}
Diese Vorgehensweise nennt sich auch Dependency Injection Dependency Injection bzw. Constructor Injection: Die benötigte Serviceinstanz wird demnach von Angular in den Konstruktor injiziert. Das bedeutet, dass Angular entscheidet, welche konkrete Ausprägung des HttpClient
die Komponente erhält. Während Angular für den Produktionsbetrieb den "richtigen" HttpClient
erzeugt, könnte es für automatisierte Tests eine Dummy-Implementierung verwenden, die HTTP-Zugriffe lediglich simuliert.
Da wir nun unsere HttpClient
-Instanz haben, können wir damit innerhalb von search
auf die Web-API zugreifen:
// src/app/flight-search/flight-search.component.ts
// Wir benötigen diese drei Importe für den HttpClient:
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { Flight } from '../flight';
@Component({
selector: 'app-flight-search',
templateUrl: './flight-search.component.html',
styleUrls: ['./flight-search.component.scss']
})
export class FlightSearchComponent implements OnInit {
from = 'Hamburg';
to = 'Graz';
flights: Array<Flight> = [];
selectedFlight: Flight | null = null;
constructor(private http: HttpClient) {
}
ngOnInit(): void {
}
search(): void {
const url = 'http://demo.ANGULARarchitects.io/api/flight';
const headers = new HttpHeaders()
.set('Accept', 'application/json');
const params = new HttpParams()
.set('from', this.from)
.set('to', this.to);
this.http.get<Flight[]>(url, {headers, params}).subscribe({
next: (flights) => {
this.flights = flights;
},
error: (err) => {
console.error('Error', err);
}
});
}
select(f: Flight): void {
this.selectedFlight = f;
}
}
Die Methode search
ruft nun bei einer von uns bereitgestellten Web API ("Rest API") Flüge ab und hinterlegt sie in der Eigenschaft flights
:
-
Die zu nutzenden HTTP-Kopfzeilen Kopfzeile HTTP-Kopfzeile stellt der
HttpClient
mit einer Instanz vonHttpHeaders
dar. Das Beispiel übergibt die KopfzeileAccept
, um anzugeben, dass wir JSON als Antwortformat wünschen. Dabei handelt es sich um das einzige Datenformat, das Angular ab Werk unterstützt. -
Die zu übersendenden URL-Parameter URL-Parameter repräsentiert der
HttpClient
mit einerHttpParams
-Auflistung. -
Bitte beachten Sie, dass die beiden Aufrufe von
set
die aktuelle Auflistung nicht verändern, sondern eine neue Auflistung zurückliefern. Deswegen verkettet das Beispiel auch die einzelnen Aufrufe vonset
. -
Die Methode
get
führt einen HTTP-Zugriff unter Verwendung der HTTP-MethodeGET
durch. Diese Methode kommt typischerweise zum Abrufen von Daten zum Einsatz. -
Als Ergebnis des HTTP-Aufrufs erwartet der
HttpClient
ein JSON-Dokument, das er in ein JavaScript-Objekt umwandelt. Den Datentyp dieses Objekts nimmtget
als Typparameter entgegen -
Das Abrufen von Daten erfolgt im Browser asynchron, also im Hintergrund. Sobald die Daten vorliegen, bringt der
HttpClient
eine der beiden beisubscribe
registrierten Methoden zur Ausführung:next
im Erfolgsfall underror
in Fehlerfall. Das Objekt, das die Methodesubscribe
anbietet, ist übrigens ein sogenanntes Observable. -
Neben der hier verwendeten Methode
get
bietet derHttpClient
noch weitere Methoden für andere Arten von HTTP-Zugriffen (siehe nachfolgende Tabelle).
Methode | Semantik |
---|---|
get |
Abrufen von Ressourcen. |
post |
Hinzufügen einer Ressource oder Anstoßen einer Verarbeitung am Server. |
put |
Hinzufügen oder Aktualisieren einer Ressource. |
patch |
Aktualisieren einer Ressource. Es müssen nur die geänderten Eigenschaften übergeben werden. |
delete |
Löschen einer Ressource. |
Der Begriff Ressource kommt aus der Welt von HTTP und bezeichnet das abgerufene oder zu sendende Objekt bzw. Dokument. Der Typparameter T
steht für den Datentyp der Antwort. Im oben betrachteten Beispiel war das Flight[]
. Jene Methoden, die Daten zum Server senden, weisen einen Parameter body
auf. Dieser nimmt das zu sendende Objekt entgegen. Für die Übertragung per HTTP wandelt der HttpClient
es in ein JSON-Objekt um. Der Parameter options
erhält ein Objekt, das die HTTP-Anfrage näher beschreibt. Im oben gezeigten Beispiel verweist es auf die zu sendenden Kopfzeilen sowie auf die zu verwendenden URL-Parameter.
Bitte beachten Sie auch, dass nicht jede Web-API alle hier beschriebenen Methoden unterstützt.
Zur Veranschaulichung erzeugt die folgende Methode einen neuen Flug.
createDemoFlight(): void {
const url = 'http://demo.ANGULARarchitects.io/api/flight';
const headers = new HttpHeaders().set('Accept', 'application/json');
const newFlight: Flight = {
id: 0,
from: 'Gleisdorf',
to: 'Graz',
date: new Date().toISOString()
};
this.http.post<Flight>(url, newFlight, { headers }).subscribe({
next: (flight) => {
console.debug('Neue Id: ', flight.id);
},
error: (err) => {
console.error('Error', err);
}
});
}
Das Beispiel geht davon aus, dass der erzeugte Flug samt der serverseitig vergebenen ID wieder zurückgeliefert wird.
Falls Sie diese Methode ausprobieren möchten, können Sie sie im Konstruktor der Komponente aufrufen (this.createDemoFlight()
).
Templates und die Datenbindung
Nachdem wir nun die Logik unserer Komponente in der Klasse FlightSearchComponent
verstaut haben, können wir uns ihrem Template zuwenden. Es handelt sich dabei um die Datei flight-search.component.html
.
Auf den ersten Blick handelt es sich hier um eine normale HTML-Datei. Neben HTML-Elementen kann sie jedoch auch sogenannteDatenbindungsausdrücke beinhalten. Damit gleicht Angular den Zustand der Komponente mit dem Zustand des Templates ab. Angular schreibt dazu beispielsweise Daten aus der Komponente in das Template oder übernimmt Eingaben in entsprechende Komponenteneigenschaften.
Eine erste Art von Datenbindungsausdruck haben Sie in Teil 1 im Rahmen der AppComponent
bereits kennengelernt: Der Ausdruck
<h1>{{title}}</h1>
hat dort den Inhalt der Eigenschaft title
ausgegeben.
Hier wollen wir nun auf weitere Arten der Datenbindung eingehen.
Two-Way-Binding
Beim Einsatz von Formularen gilt es häufig, Eigenschaften aus der Komponente mit Eingabefeldern in der Anwendung abzugleichen: Die Werte der Eigenschaften sind also in Formularfelder zu übernehmen. Ändert der Anwender diese Felder, sind die neuen Werte in die jeweiligen Eigenschaften zurückzuschreiben. Diese Aufgabe übernimmt Angular mit sogenannten Two-Way-Bindings.
Wenn Sie mit einem Two-Way-Binding beispielsweise die Eigenschaft from
aus unserer FlightSearchComponent
an ein Eingabefeld binden wollen, müssen Sie in Angular folgende Schreibweise nutzen:
<input [(ngModel)]="from" name="from">
Kommt
input
innerhalb einesform
-Elements zum Einsatz, muss es auch einname
-Attribut aufweisen. Angular nutzt diesen Wert zum Aufbau interner Datenstrukturen.
Damit Sie auf den ersten Blick erkennen, dass es sich hier um ein Two-Way-Binding handelt, nutzt Angular eckige Klammern in Kombination mit runden. Die Community nennt diese Schreibkonvention auch Banana-in-a-Box. Zugegeben, dieser Einsatz von Sonderzeichen wirkt zunächst ein wenig seltsam. Allerdings hat sich das Angular-Team ganz bewusst für diese Schreibweise entschieden, um die Art der Datenbindung offensichtlich zu machen.
Bei ngModel
handelt es sich um eine sogenannte Direktive. Direktiven sind von Angular bereitgestellte DOM-Erweiterungen, die Verhalten zur Seite hinzufügen. Im Fall von ngModel
besteht dieses Verhalten im gewünschten Abgleich mit der angegebenen Eigenschaft. Gewissermaßen ist ngModel
ein Experte für Eingabefelder: Es weiß, wie es die verschiedenen Eingabefelder – darunter Textfelder, Checkboxen, Radioboxen und Drop-down-Felder – mit den angegebenen Eigenschaften abgleichen kann.
Damit ngModel
zur Verfügung steht, muss das FormsModule
in unser
AppModule
importiert werden:
// src/app/app.module.ts
[...]
// Diese Zeile einfügen:
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
// Diesen Eintrag hinzufügen:
FormsModule,
[...]
],
declarations: [
[...]
],
providers: [],
bootstrap: [
AppComponent
]
})
export class AppModule { }
Two-Way-Data-Binding funktioniert nur mit ausgewählten Eigenschaften. Unter diesen ist
ngModel
die einzige, die Angular ab Werk zur Verfügung steht. Sie können jedoch eigene Eigenschaften, die Two-Way-Data-Binding unterstützen, entwickeln. Details dazu finden Sie im nächsten Teil dieses Tutorials.
Property-Bindings
Ähnlich wie Two-Way-Bindings übernehmen Property-Bindings Eigenschaften aus der Komponente in das Markup. Auch nach dem Aktualisieren der Eigenschaften in der Komponente aktualisiert diese Binding-Art die Ausgabe. Allerdings schreibt sie Änderungen des Benutzers nicht mehr in die Komponente zurück. Deswegen könnte man hier auch von One-Way-Bindings sprechen.
Um solch ein Binding einzurichten, nutzen Sie eckige Klammern:
<button [disabled]="!from || !to">Search</button>
Das hier betrachtete Beispiel bindet den Ausdruck !from || !to
an die DOM-Eigenschaft disabled
. Der Ausdruck prüft, ob mindestens eine der beiden Eigenschaften leer ist. Das Beispiel deaktiviert somit die Schaltfläche, wenn keine Werte für diese Eigenschaften vorliegen.
Das Beispiel zeigt auch, dass Angular sich an standardmäßig vorherrschende DOM-Eigenschaften binden kann. Genau genommen, ist es aus Sicht von Angular egal, warum eine DOM-Eigenschaft existiert. Sowohl Standardeigenschaften als auch eigene Eigenschaften wie ngModel
im letzten Abschnitt sowie DOM-Erweiterungen von anderen Bibliotheken lassen sich zusammen mit der Datenbindung nutzen.
Eine weitere Schreibweise für One-Way-Bindings sieht den bereits diskutierten Einsatz geschweifter Klammern vor:
<div>Es wurden {{ selectedFlight.length }} Flüge gefunden</div>
Damit platziert Angular eine Eigenschaft bzw. einen darauf basierenden Ausdruck mitten in der Seite.
Direktiven
Wie bereits erwähnt, fügen Direktiven der Seite Verhalten hinzu. Dieses kann die Datenbindung unterstützen. Ein Beispiel dafür ist die Direktive ngFor
, die eine Auflistung iteriert und pro Eintrag ein Stück HTML rendert:
<table class="table table-striped">
<tr *ngFor="let flight of flights">
<td>{{flight.id}}</td>
<td>{{flight.from}}</td>
<td>{{flight.to}}</td>
<td>{{flight.date}}</td>
</tr>
</table>
Im hier betrachteten Fall durchläuft ngFor
sämtliche Flüge des Arrays flights
aus der Komponente des vorherigen Abschnitts. Pro Flug rendert sie eine Tabellenzeile. Bitte beachten Sie, dass in Anlehnung an die for-of
-Schleife in ECMAScript auch hier im Rahmen der Datenbindung das Schlüsselwort of
zu verwenden ist.
Der vorangestellte Stern (*ngFor
) gibt darüber Auskunft, dass es sich beim Inhalt des aktuellen Elements um ein sogenanntes Template handelt. Damit sind hier HTML-Fragmente gemeint, die Angular zunächst gar nicht rendert und bei Bedarf einmal oder mehrere Male in die Seite einfügt.
<table class="table table-striped">
<tr *ngFor="let flight of flights"
[ngClass]="{ 'active': flight === selectedFlight }">
[...]
</tr>
</table>
Somit erhält die Tabellenzeile mit dem gerade ausgewählten Flug die Klasse active
. Dieser Style kann in der Datei flight-search.component.scss
definiert werden:
.active {
background-color:darkorange
}
In diesem Fall gilt der Style nur für die FlightSearchComponent
. Um ihn global zur Verfügung zu stellen, ist er in die Datei src/styles.scss
einzutragen.
Pipes
Ähnlich wie Direktiven unterstützen auch Pipes die Datenbindung. Sie sind in der Lage, Werte beim Binden zu verändern, und lassen sich somit unter anderem für das Formatieren von Werten nutzen. Zur Demonstration nutzt das folgende Beispiel die von Angular angebotene Pipe date
zum Formatieren des Datums:
<td>{{flight.date | date:'dd.MM.yyyy HH:mm'}}</td>
Eine weitere standardmäßig vorhandene Pipe, die vor allem Entwicklern hilft, ist die Pipe json
. Sie wandelt das gesamte Objekt in seine JSON-Repräsentation um. Somit können Entwickler Objekte zum Testen ausgeben, ohne dafür eine Komponente oder Markup schreiben zu müssen:
<b>Basket</b>
<pre>{{ selectedFlight | json }}</pre>
Event-Bindings
Runde Klammern führen zu einer Bindung an Events. Dabei kann es sich sowohl um DOM-Events als auch um Erweiterungen von Frameworks wie Angular handeln. Das hier betrachtete Beispiel nutzt zwei Event-Bindings, um auf Mausklicks zu reagieren. Das eine Event-Binding verknüpft die Schaltfläche Search mit der Komponentenmethode search
:
<button (click)="search()" [disabled]="!from || !to">
Search
</button>
Das andere Event-Binding ruft für einen der dargestellten Flüge die Methode select
auf, um ihn als ausgewählten Flug vorzumerken:
<table class="table table-striped">
<tr *ngFor="let flight of flights"
[ngClass]="{ 'active': flight === selectedFlight }">
[…]
<td><a (click)="select(flight)">Select</a></td>
</tr>
</table>
Verwenden Sie das folgende Styling in der Datei
src/styles.scss
, damit der Browser auch für Anchor-Tags ohnehref
-Attribut den typischen Mauscursor für klickbare Links (Zeigefingersymbol) anzeigt:a { cursor: pointer; }
Das gesamte Template Template
Der Vollständigkeit halber platzieren wir hier nochmal das gesamte
Template für die FlightSearchComponent
, das wir in den vorangegangenen
Abschnitten besprochen haben:
<!-- src/app/flight-search/flight-search.component.html -->
<h1>Flight Search</h1>
<div class="form-group">
<label>From:</label>
<input [(ngModel)]="from" class="form-control">
</div>
<div class="form-group">
<label>To:</label>
<input [(ngModel)]="to" class="form-control">
</div>
<div class="form-group">
<button class="btn btn-default" (click)="search()" [disabled]="!from || !to">
Search
</button>
</div>
<table class="table table-striped">
<tr *ngFor="let flight of flights"
[ngClass]="{ 'active': flight === selectedFlight }">
<td>{{flight.id}}</td>
<td>{{flight.from}}</td>
<td>{{flight.to}}</td>
<td>{{flight.date | date:'dd.MM.yyyy HH:mm'}}</td>
<td><a (click)="select(flight)">Select</a></td>
</tr>
</table>
<b>Basket</b>
<pre>{{ selectedFlight | json }}</pre>
Dabei fällt auf, dass die verwendeten Sonderzeichen, die bei ersten Schritten mit Angular durchaus gewöhnungsbedürftig sind, uns beim Erkennen der gewählten Datenbindungsart unterstützen und das Template somit nachvollziehbarer gestalten.
Komponenten einbinden
Nachdem wir nun eine erste eigene Komponente geschaffen haben, müssen wir sie nur noch in unsere Anwendung einbinden. Damit die Angular-Anwendung unsere Komponente überhaupt berücksichtigen kann, muss sie in einem Angular-Modul deklariert werden. In unserem Fall handelt es sich dabei um das AppModule
.
Diese Aufgabe sollte die CLI beim Generieren der Komponente schon übernommen haben. Aber zur Sicherheit lohnt es sich, das zu überprüfen. Öffnen Sie dazu die Datei app.module.ts und vergewissern Sie sich, dass die FlightSearchComponent
unter declarations
eingetragen ist:
// src/app/app.module.ts
[...]
import { AppComponent } from './app.component';
[...]
@NgModule({
imports: [
FormsModule,
HttpClientModule,
BrowserModule
],
declarations: [
AppComponent,
SidebarComponent,
NavbarComponent,
// Unsere Komponente:
FlightSearchComponent
],
providers: [],
bootstrap: [
AppComponent
]
})
export class AppModule { }
Danach können wir die Komponente im Template der AppComponent
aufrufen:
<div class="wrapper">
<div class="sidebar" data-color="white" data-active-color="danger">
<app-sidebar-cmp></app-sidebar-cmp>
</div>
<div class="main-panel">
<app-navbar-cmp></app-navbar-cmp>
<div class="content">
<!-- Alt: -->
<!-- <h1>{{title}}</h1> -->
<!-- Diese Zeile einfügen: -->
<app-flight-search></app-flight-search>
</div>
</div>
</div>
Anwendung starten
Gratulation! Sie haben Ihre erste Angular-Anwendung geschrieben, und es ist nun an der Zeit, sie auszuführen.
Zum Starten Ihrer Anwendung nutzen Sie die Angular CLI im Projekthauptverzeichnis:
ng serve -o
Nach dem Start des Entwicklungswebservers steht die Anwendung unter http://localhost:4200 bereit:
Fehler in der Entwicklerkonsole entdecken
Verhält sich die Anwendung nicht wie gewünscht, sollten Sie einen Blick
auf die Konsole in den Entwicklerwerkzeugen (F12 oder
Strg+Umschalt+I) werfen. Hier finden Sie häufig Fehlermeldungen:
Der gezeigte Fehler wurde zur Veranschaulichung mit der Anweisung
throw new Error('Manfred braucht einen Kaffee!');
am Anfang der Methode search
provoziert. In der Regel ist das jedoch nicht notwendig: Anwendungen weisen häufig auch ohne weiteres Zutun Bugs auf ;-).
Bitte beachten Sie die Hyperlinks, die Angular im Rahmen der Fehlermeldung ausgibt. Diese führen zu Zeilen in den betroffenen HTML-und TypeScript-Dateien, die beim Auftreten des Fehlers durchlaufen wurden.
Bonus: Die Anwendung im Browser debuggen
In Fällen, in denen Sie die Ursache des Fehlers nicht finden, können Sie auch den in den Browser integrierten JavaScript-Debugger einsetzen. Die Voraussetzung dafür ist, dass die CLI Metadaten für den Debugger – sogenannte Source-Maps – generiert hat. Beim Einsatz von ng serve
ist das standardmäßig der Fall.
Bei Chrome finden Sie den Debugger in den Entwicklerwerkzeugen auf dem Registerblatt Sources:
Hier können Sie Ihre Programmdateien öffnen und durch einen Klick auf eine Zeilennummer auf der linken Seite einen Break Point definieren. Zum Öffnen Ihrer Programmdateien empfiehlt sich die Tastenkombination Strg+Umschalt+P. Diese öffnet einen Dialog, mit dem Sie nach der gewünschten Datei suchen können. Geben Sie dazu einfach die ersten Buchstaben des Dateinamens ein.
Gelangt die Programmausführung zur Zeile mit dem Break Point, wird die Anwendung angehalten. Danach können Sie mit den Schaltflächen links oben die Ausführung Schritt für Schritt fortsetzen und z. B. die aktuellen Werte Ihrer Variablen und Eigenschaften einsehen.
Bonus: Debuggen mit Visual Studio Code
Etwas komfortabler lässt sich der in Chrome integrierte Debugger über Visual Studio Code bedienen. Damit das möglich ist, müssen Sie das Visual-Studio-Code-Plug-in Debugger for Chrome installiert haben.
Zum Starten des Debuggers via Visual Studio Code benötigen Sie die Datei .vscode/launch.json. Falls sie noch nicht existiert, können Sie sie mit den folgenden Schritten einrichten:
-
Öffnen Sie eine beliebige
.ts
-Datei. -
Wählen Sie in Visual Studio Code den Befehl
Run/Start Debugging
oder drücken SieF5
. -
Falls Visual Studio Code Sie nach einer Umgebung (Environment) für das Debugging fragt, wählen Sie Chrome aus.
-
Visual Studio Code generiert nun eine Datei
launch.json
und zeigt diese an. -
Korrigieren Sie in der Datei
launch.json
die angezeigte URL aufhttp://localhost:4200
:{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome against localhost", "url": "http://localhost:4200", "webRoot": "${workspaceFolder}" } ] }
Wenn alle Stricke reißen, können Sie diese Datei auch manuell anlegen.
Um den Debugger nun via Visual Studio Code zu nutzen, sind die folgenden Schritte notwendig:
-
Starten Sie Ihre Anwendung wie gewohnt mit
ng serve
. -
Erzeugen Sie direkt in Visual Studio Code durch einen Klick links neben eine Zeilennummer einen Break Point:
-
Wählen Sie den Befehl
Run/Start Debugging
oder drücken SieF5
. -
Nun öffnet sich Chrome.
-
Sobald der Programmfluss auf den Break Point stößt, hält der Debugger die Anwendung an.
-
Sie können den Debugger jetzt direkt aus Visual Studio Code heraus steuern, die Ausführung Schritt für Schritt fortsetzen und die Werte von Variablen bzw. Eigenschaften einsehen.
Zusammenfassung aus Ausblick
Angular-Anwendungen bestehen aus Komponenten. Hierbei handelt es sich um Klassen, die Informationen über Eigenschaften sowie das gewünschte Verhalten über Methoden anbieten. Dazugehörige Templates definieren, wie Angular die Komponenten darstellt. Mit Datenbindungsausdrücken stellen sie die Eigenschaften dar und verknüpfen Methoden mit UI-Ereignissen.
Im nächsten Teil dieses Tutorials lagern wir wiederverwendbare Programmteile in Sub-Komponenten und Services aus.
Unsere Angular-Schulungen
