1. Angular Tutorial – Part 1: Your First Application
  2. Angular Tutorial – Part 2: Components, Data Binding, and HTTP Access
  3. Angular Tutorial – Part 3: Reusable Sub-Components and Services
  4. Angular Tutorial – Part 4: Navigation Structures with the Angular Router

Throughout this tutorial, a single running example is used to illustrate the various concepts in Angular. The code is available in our GitHub repository and consists of a flight booking application. We build directly on the project that was generated in the previous part of the tutorial:

Anwendung zum Suchen nach Flügen

Creating an Interface for the Data Model

Since the application works with flights, a type that mirrors the shape of a flight object is required. To that end, we first create a file named flight.ts inside the src/app directory, containing the following interface:

// src/app/flight.ts
export interface Flight { 
    id: number; 
    from: string; 
    to: string; 
    date: string; 
    delayed?: boolean;
}

Generating an Angular Component

Next, we create an Angular component that handles the use case under discussion. Switch to the console and run the following command in the root directory of the application — the folder that contains the angular.json file:

ng generate component flight-search

The CLI allows abbreviated command forms. The instruction shown above could also be written like this:

ng g c flight-search

The Angular Schematics extension for Visual Studio, which was mentioned in part 1, lets you execute this same CLI command right from Visual Studio Code. Simply pick the Angular: Generate a component entry from the context menu of the folder you wish to target.

Komponente in Visual Studio Code generieren

Once you have selected that entry, Visual Studio Code will ask you a few questions. For the component name, provide the same value used in the command above, namely flight-search. Any remaining questions can be confirmed with Enter, thereby accepting the CLI defaults.

Afterwards, the Angular CLI produces several files that belong to the new component:

Komponente zum Suchen nach Flügen mit der CLI generieren

These files are placed in the src/app/flight-search folder:

  • flight-search.component.html: The component template. It controls how Angular renders the component on screen.
  • flight-search.component.ts: The TypeScript class that implements the component. It defines the component's behavior.
  • flight-search.component.scss: A stylesheet holding the local styles for the component.

The flight-search.component.ts and flight-search.component.html files are examined in detail in the sections that follow and will be customized to fit our needs.

Component Logic

The generated flight-search.component.ts file contains the basic skeleton for the component's logic:

// 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 {
    }

}

Most of the constructs you see here were already discussed in part 1 of this series in connection with the AppComponent. Still, a few points deserve special attention:

  • The selector is named app-flight-search. The CLI has prepended the app prefix, a convention that is meant to keep component names from colliding with those in third-party libraries.
  • The class itself is called FlightSearchComponent, while the containing file goes by the name flight-search.component.ts. Both names follow the typical conventions used throughout the Angular ecosystem.
  • FlightSearchComponent implements the OnInit interface, which in turn requires a method called ngOnInit. Angular invokes this method once the component has been initialized, making it a good place to initialize properties.

Let us now extend the skeleton so that it can search for flights:

// 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;
    }

}

The properties from and to hold the search criteria that are used when looking for flights. Their default values are meant to save us from typing in the same criteria over and over again. At the same time, they make it easy to spot whether the automatic synchronization between these properties and the input fields — which we will set up shortly — actually works.

The flights array stores all flights that come back from a search. It is strongly typed with the Flight interface created earlier.

The selectedFlight property tracks the flight that the user has picked. Since it must be possible to initialize it to null, its type is declared as Flight | null.

By default, Angular uses TypeScript in strict mode. Among other things, that means you have to explicitly state whether a property is allowed to hold null or undefined. In those situations, TypeScript also forces you to check for those values before using them.

The search method takes care of loading the flights. Its implementation is discussed in a moment. The select method remembers which flight the user has chosen.

Become an Angular Pro with Our Training Courses

From the Angular practice training for beginners and self-taught developers over Advanced workshops for experienced developers up to Angular deep dives for seasoned users. In our seminars, participants learn everything a developer needs for successful Angular projects.

Accessing the Backend

For its main task, the FlightSearchComponent needs to talk to a web API that exposes flights via HTTP. Angular offers the HttpClient class for exactly such scenarios. Since this class provides a reusable service, it is often simply referred to as a service.

Before you can use the service, the HttpClientModule has to be imported into your AppModule:

// 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 { }

Once that is in place, you can ask for an instance of HttpClient through the constructor of the FlightSearchComponent:

// 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) {
    }

    [...]

}

This approach is known as Dependency Injection, more precisely Constructor Injection: the required service instance is supplied by Angular through the constructor. It is therefore Angular that decides which concrete form of the HttpClient the component receives. While Angular creates the real HttpClient for production use, it could inject a dummy implementation solely for automated tests, one that merely emulates HTTP calls.

Now that we hold an HttpClient instance, we can use it inside search to fetch data from the web API:

// 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;
    }

}

The search method now retrieves flights from a web API (a "REST API") and stores them in the flights property:

  • The HTTP headers that should be sent are represented by the HttpClient using an instance of HttpHeaders. The example passes the Accept header to signal that JSON is the desired response format. JSON is the only format that Angular supports out of the box.
  • The URL parameters that should be sent along are represented by the HttpClient using an HttpParams collection.
  • Note that the two calls to set do not modify the collection they are called on; instead, each of them returns a new collection. For that reason, the example chains the calls to set one after another.
  • The get method performs an HTTP request using the HTTP verb GET. This verb is typically used to retrieve data.
  • As the result of the HTTP call, the HttpClient expects a JSON document, which it converts into a JavaScript object. The type of that object is supplied to get as a type parameter.
  • Fetching data in the browser happens in the background, i.e., asynchronously. Once the data has arrived, the HttpClient invokes one of the two callbacks registered via subscribe: next in case of success and error in case of failure. The object to which subscribe is attached is known as an Observable.
  • Besides the get method shown here, HttpClient offers further methods for other kinds of HTTP requests, as summarized in the following table.
Methode Semantik
get(url, options) Abrufen von Ressourcen.
post(url, body, options) Hinzufügen einer Ressource oder Anstoßen einer Verarbeitung am Server.
put(url, body, options) Hinzufügen oder Aktualisieren einer Ressource.
patch(url, body, options) Aktualisieren einer Ressource. Es müssen nur die geänderten Eigenschaften übergeben werden.
delete(url, options) Löschen einer Ressource.

The term resource originates from the HTTP world and denotes the object or document that is being retrieved or sent. The type parameter T stands for the type of the response. In the example above, that was Flight[]. All methods that send data to the server accept a body parameter, which carries the object to be transmitted. For transfer over HTTP, the HttpClient converts this object into JSON. The options parameter receives an object that further describes the HTTP request. In the example above, it refers to the headers and URL parameters to be used.

Also keep in mind that not every web API supports all of the methods described here.

As an illustration, the following method creates a new flight.

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);
        }
    });
}

In this example, it is assumed that the created flight, including the ID assigned by the server, is sent back as the response.

If you would like to try this method, you can invoke it from the component's constructor by calling this.createDemoFlight().

Templates und Datenbindung

Nachdem die Logik der Komponente in der Klasse FlightSearchComponent untergebracht ist, richtet sich der Blick auf das zugehörige Template. Dieses liegt in der Datei flight-search.component.html.

Auf den ersten Blick wirkt es wie eine gewöhnliche HTML-Datei. Neben den üblichen HTML-Elementen können darin jedoch auch Ausdrücke für die Datenbindung vorkommen. Auf diese Weise gleicht Angular den Zustand der Komponente mit dem des Templates ab. Konkret bedeutet das: Angular überträgt Daten aus der Komponente in das Template oder übernimmt Benutzereingaben in die entsprechenden Eigenschaften der Komponente.

Eine erste Variante eines solchen Bindungsausdrucks ist bereits in Teil 1 bei der AppComponent aufgetaucht: Mit dem Ausdruck

<h1>{{title}}</h1>

wurde damals der Inhalt der Eigenschaft title angezeigt.

Nun sollen weitere Formen der Datenbindung betrachtet werden.

Two-Way-Datenbindung

Bei Formularen besteht häufig die Notwendigkeit, Eigenschaften aus der Komponente mit Eingabefeldern der Oberfläche zu synchronisieren: Die Werte der Eigenschaften müssen in den Feldern erscheinen, und wenn der Benutzer sie ändert, sollen die neuen Werte zurück in die Eigenschaften fließen. Diese Aufgabe übernimmt Angular mithilfe der sogenannten Two-Way-Bindings.

Möchte man etwa die Eigenschaft from aus der FlightSearchComponent an ein Eingabefeld binden, sieht die Angular-Schreibweise dafür wie folgt aus:

<input [(ngModel)]="from" name="from">

Wenn ein input-Element innerhalb eines form-Elements verwendet wird, ist ein name-Attribut Pflicht. Angular benötigt diesen Wert, um interne Datenstrukturen aufzubauen.

Damit auf einen Blick erkennbar ist, dass es sich um eine Two-Way-Bindung handelt, kombiniert Angular eckige und runde Klammern. In der Community hat sich dafür die Bezeichnung Banana-in-a-Box etabliert. Auf den ersten Blick wirkt diese Zeichenkombination etwas ungewöhnlich. Das Angular-Team hat sich jedoch bewusst für diese Schreibweise entschieden, damit die jeweilige Bindungsart sofort ersichtlich ist.

ngModel ist eine sogenannte Direktive. Direktiven sind von Angular bereitgestellte Erweiterungen für das DOM, die der Seite zusätzliches Verhalten verleihen. Bei ngModel besteht dieses Verhalten in der gewünschten Synchronisierung mit der angegebenen Eigenschaft. Man kann ngModel gewissermaßen als Spezialisten für Eingabefelder betrachten: Es kennt die verschiedenen Feldtypen – etwa Textfelder, Checkboxen, Radioboxen oder Auswahllisten – und gleicht sie mit den jeweiligen Eigenschaften ab.

Damit ngModel verwendet werden kann, muss das FormsModule in das 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-Datenbindung funktioniert nur mit ausgewählten Eigenschaften. Unter ihnen ist ngModel die einzige, die Angular standardmäßig mitliefert. Es ist allerdings möglich, eigene Eigenschaften zu entwickeln, die eine Two-Way-Bindung unterstützen. Mehr dazu folgt im nächsten Teil dieses Tutorials.

Property-Bindings

Property-Bindings ähneln den Two-Way-Bindings, da sie Werte aus der Komponente ins Markup übertragen. Auch hier wird die Darstellung aktualisiert, sobald sich die Eigenschaften in der Komponente ändern. Der Unterschied liegt darin, dass Benutzereingaben nicht mehr in die Komponente zurückgeschrieben werden. Deshalb spricht man hier auch von One-Way-Bindings.

Für diese Bindungsart kommen eckige Klammern zum Einsatz:

<button [disabled]="!from || !to">Search</button>

In diesem Beispiel wird der Ausdruck !from || !to an die DOM-Eigenschaft disabled gebunden. Der Ausdruck ermittelt, ob mindestens eine der beiden Eigenschaften leer ist. Auf diese Weise wird die Schaltfläche deaktiviert, solange keine Werte für diese Felder vorhanden sind.

Das Beispiel verdeutlicht außerdem, dass sich Angular an die im DOM üblichen Eigenschaften binden kann. Aus Sicht von Angular spielt es keine Rolle, warum eine DOM-Eigenschaft existiert. Sowohl Standardeigenschaften als auch eigene Eigenschaften wie ngModel aus dem vorherigen Abschnitt sowie DOM-Erweiterungen anderer Bibliotheken lassen sich mit der Datenbindung kombinieren.

Eine weitere Variante der One-Way-Bindung verwendet die bereits diskutierten geschweiften Klammern:

<div>Es wurden {{ selectedFlight.length }} Flüge gefunden</div>

Damit platziert Angular eine Eigenschaft oder einen darauf beruhenden Ausdruck direkt in der Seite.

Direktiven

Wie bereits erwähnt, erweitern Direktiven die Seite um Verhalten, das auch die Datenbindung unterstützen kann. Ein Beispiel hierfür ist die Direktive ngFor, die eine Liste durchläuft und für jeden Eintrag einen Ausschnitt 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 konkreten Fall iteriert ngFor durch alle Flüge des Arrays flights aus der Komponente des vorherigen Abschnitts. Für jeden Flug wird eine Tabellenzeile erzeugt. Zu beachten ist, dass analog zur for-of-Schleife in ECMAScript auch hier in der Datenbindung das Schlüsselwort of verwendet wird.

Der vorangestellte Stern (*ngFor) zeigt an, dass der Inhalt des aktuellen Elements als sogenanntes Template zu verstehen ist. Damit sind HTML-Fragmente gemeint, die Angular zunächst gar nicht ausgibt und bei Bedarf einmal oder mehrfach in die Seite einfügt.

<table class="table table-striped">
    <tr *ngFor="let flight of flights"
        [ngClass]="{ 'active': flight === selectedFlight }">

    [...]

    </tr>
</table>

Auf diese Weise erhält die Tabellenzeile des gerade ausgewählten Fluges die Klasse active. Dieses Styling lässt sich in der Datei flight-search.component.scss definieren:

.active {
    background-color:darkorange
}

In diesem Fall beschränkt sich der Style auf die FlightSearchComponent. Für eine globale Verfügbarkeit müsste er in die Datei src/styles.scss aufgenommen werden.

Pipes

Pipes unterstützen die Datenbindung ähnlich wie Direktiven. Sie können Werte während des Bindens verändern und eignen sich daher unter anderem zum Formatieren. Zur Veranschaulichung verwendet das folgende Beispiel die von Angular bereitgestellte Pipe date, um ein Datum zu formatieren:

<td>{{flight.date | date:'dd.MM.yyyy HH:mm'}}</td>

Eine weitere vorinstallierte Pipe, die vor allem Entwicklern nützt, ist json. Sie wandelt ein komplettes Objekt in seine JSON-Darstellung um. Damit lassen sich Objekte zu Testzwecken darstellen, ohne eine Komponente oder zusätzliches Markup erstellen zu müssen:

<b>Basket</b>
<pre>{{ selectedFlight | json }}</pre>

Event-Bindings

Runde Klammern führen zu einer Bindung an Ereignisse. Dabei kann es sich um DOM-Events oder um von Frameworks wie Angular bereitgestellte Erweiterungen handeln. Das betrachtete Beispiel nutzt zwei Event-Bindings, um auf Mausklicks zu reagieren. Die eine Bindung verknüpft die Schaltfläche Search mit der Komponentenmethode search:

<button (click)="search()" [disabled]="!from || !to">
Search
</button>

Die andere Bindung ruft für einen der angezeigten Flüge die Methode select auf, um ihn als ausgewählten Flug zu markieren:

<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 bei Anchor-Tags ohne href-Attribut den für klickbare Links typischen Mauszeiger (Zeigefingersymbol) anzeigt:

a {
   cursor: pointer;
}

Das komplette Template im Überblick

Der Vollständigkeit halber sei hier noch einmal das gesamte Template der FlightSearchComponent abgebildet, das in den vorangegangenen Abschnitten erläutert wurde:

<!-- 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 wird deutlich: Die ungewöhnlichen Sonderzeichen, die bei den ersten Schritten mit Angular durchaus gewöhnungsbedürftig erscheinen mögen, helfen dabei, die jeweils gewählte Bindungsart sofort zu erkennen, und machen das Template dadurch verständlicher.

Einbinden von Komponenten

Nachdem eine erste eigene Komponente erstellt wurde, muss sie nur noch in die Anwendung eingebunden werden. Damit Angular die Komponente überhaupt berücksichtigt, muss sie in einem Angular-Modul deklariert sein. Im vorliegenden Fall ist das das AppModule.

Die CLI sollte diese Aufgabe beim Generieren der Komponente bereits erledigt haben. Zur Sicherheit lohnt aber ein kurzer Blick in die Datei app.module.ts, um sicherzustellen, dass die FlightSearchComponent unter declarations aufgeführt 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 { }

Im Anschluss lässt sich die Komponente im Template der AppComponent einbinden:

<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>

Starten der Anwendung

Herzlichen Glückwunsch – die erste Angular-Anwendung steht! Jetzt wird es Zeit, sie zu starten.

Zum Ausführen der Anwendung wird die Angular CLI im Hauptverzeichnis des Projekts aufgerufen:

ng serve -o

Nach dem Start des Entwicklungswebservers ist die Anwendung unter http://localhost:4200 erreichbar:

Ihre erste Komponente

Fehlersuche in der Entwicklerkonsole

Wenn sich die Anwendung nicht wie erwartet verhält, lohnt ein Blick auf die Konsole der Entwicklerwerkzeuge (F12 oder Strg+Umschalt+I). Dort sind häufig Fehlermeldungen zu finden:

Fehler in der Entwicklerkonsole

Der abgebildete Fehler wurde zu Demonstrationszwecken mit der Anweisung

throw new Error('Manfred braucht einen Kaffee!');

am Beginn der Methode search absichtlich herbeigeführt. Meistens ist das allerdings nicht nötig: Anwendungen besitzen häufig auch ohne weiteres Zutun genügend Fehlerquelle ;-).

Beachtenswert sind die Hyperlinks, die Angular in der Fehlermeldung ausgibt. Sie führen direkt zu den betroffenen Zeilen in den HTML- und TypeScript-Dateien, die beim Auftreten des Fehlers aufgerufen wurden.

Zusatz: Debuggen im Browser

Wenn die Ursache eines Fehlers nicht gefunden werden kann, lässt sich auch der in den Browser integrierte JavaScript-Debugger verwenden. Voraussetzung ist, dass die CLI die dafür nötigen Metadaten – die sogenannten Source-Maps – erzeugt hat. Mit ng serve ist das standardmäßig der Fall.

In Chrome befindet sich der Debugger in den Entwicklerwerkzeugen auf dem Tab Sources:

JavaScript-Debugger in Chrome

Dort lassen sich die Programmdateien öffnen, und über einen Klick auf die Zeilennummer am linken Rand wird ein Breakpoint gesetzt. Zum Öffnen der Dateien eignet sich die Tastenkombination Strg+Umschalt+P. Sie öffnet einen Dialog, in dem nach der gewünschten Datei gesucht werden kann – die ersten Buchstaben des Dateinamens genügen.

Erreicht die Programmausführung die Zeile mit dem Breakpoint, hält die Anwendung an. Mit den Schaltflächen links oben lässt sich die Ausführung dann Schritt für Schritt fortsetzen, während zugleich die aktuellen Werte von Variablen und Eigenschaften eingesehen werden können.

Zusatz: Debuggen über Visual Studio Code

Etwas komfortabler wird die Arbeit mit dem Chrome-Debugger, wenn Visual Studio Code als Frontend dient. Dafür muss das Visual-Studio-Code-Plug-in Debugger for Chrome installiert sein.

Um den Debugger über Visual Studio Code zu starten, ist die Datei .vscode/launch.json erforderlich. Falls sie noch nicht existiert, kann sie wie folgt angelegt werden:

  1. Eine beliebige .ts-Datei öffnen.
  2. In Visual Studio Code den Befehl Run/Start Debugging auswählen oder F5 drücken.
  3. Wenn Visual Studio Code nach einer Umgebung (Environment) fragt, Chrome wählen.
  4. Visual Studio Code erstellt daraufhin eine Datei launch.json und zeigt sie an.
  5. In der Datei launch.json muss die angezeigte URL auf http://localhost:4200 korrigiert werden:
    {
        "version": "0.2.0",
        "configurations": [
            {
                "type": "chrome",
                "request": "launch",
                "name": "Launch Chrome against localhost",
                "url": "http://localhost:4200",
                "webRoot": "${workspaceFolder}"
            }
        ]
    }

Sollte dieser Weg nicht funktionieren, kann die Datei auch von Hand angelegt werden.

Für die Nutzung des Debuggers über Visual Studio Code sind dann diese Schritte erforderlich:

  1. Die Anwendung wie gewohnt mit ng serve starten.
  2. Direkt in Visual Studio Code neben einer Zeilennummer per Klick einen Breakpoint setzen:

    Break Point in Visual Studio Code (Zeile 62)

  3. Run/Start Debugging auswählen oder F5 drücken.
  4. Daraufhin öffnet sich Chrome.
  5. Stößt der Programmablauf auf den Breakpoint, hält der Debugger die Anwendung an.
  6. Der Debugger lässt sich nun direkt aus Visual Studio Code steuern, schrittweise Ausführung inklusive – Werte von Variablen und Eigenschaften sind dabei jederzeit einsehbar.

    Debuggen mit Visual Studio Code

Zusammenfassung und Ausblick

Angular-Anwendungen setzen sich aus Komponenten zusammen. Dabei handelt es sich um Klassen, die über Eigenschaften Daten und über Methoden Verhalten zur Verfügung stellen. Die zugehörigen Templates legen fest, wie Angular die Komponenten darstellt. Mit Hilfe von Datenbindungsausdrücken geben sie Eigenschaften aus und verbinden Methoden mit Ereignissen der Benutzeroberfläche.

Im nächsten Teil dieses Tutorials werden wiederverwendbare Programmbausteine in Sub-Komponenten und Services ausgelagert.