, ,

Einführung in Angular-Komponenten (Part 1)


Im Rahmen seiner Tätigkeit in der IT kommt man um bestimmte Frameworks nicht herum. Oft werden sogar mehrere Frameworks in einem einzigen Projekt verwendet, da jedes einen anderen Zweck erfüllt. Persönlich bin ich nun schon zum zweiten Mal auf Angular gestoßen. Von daher lade ich dich dazu ein, dass wir hier gemeinsam eine kleine, unkomplizierte Anwendung in diesem Framework erstellen und die verschiedenen Komponenten daraufhin testen. Wir halten uns hierbei an das K.I.S.S.-Prinzip (Keep It Simple and Stupid), was bedeutet, dass ich in diesem Blog nur die grundlegenden Konzepte erläutere, um das Verständnis zu erleichtern. In diesem Beitrag arbeite ich unter Windows, verwende die Eingabeaufforderung (cmd.exe) als Terminal innerhalb von Visual Studio Code als IDE. Selbstverständlich steht es dir frei, unter einem anderen Betriebssystem, mit einem anderen Terminal oder auch mit einer anderen IDE zu arbeiten. Ich bitte dich jedoch, dass du dich selbstständig mit diesen vertraut machst, insbesondere wenn es darum geht, etwas zu installieren.


Anmerkung: In diesem Beitrag nutze ich die informelle Anrede „du“ und gleichzeitig das generische Maskulinum. Dies dient dazu, eine persönliche Atmosphäre zu schaffen, die den Austausch und das Lernen ansprechender macht. Gleichzeitig erleichtert es den Lesefluss. Dennoch gilt natürlich allen mein höchster Respekt.


Einleitung

Für wen ist dieser Artikel?

Dieser Blog-Beitrag richtet sich an dich, wenn du dich zum ersten Mal mit Angular beschäftigst. Hier werden die Grundlagen vermittelt, um eine Angular-Anwendung zu erstellen und sie mit Karma und Jasmine zu testen. Es ist wichtig, dass du über folgende Grundkenntnisse verfügst:

  • Terminologie:
    • Page Object Modell
    • Komponenten
    • Services (Dienste)
  • Technologien:
    • HTML
    • (S)CSS
    • JavaScript / TypeScript
    • Node.js
    • Testing-Frameworks (allgemein)
    • XPath (o.ä.)

Was ist das Ziel?

Das Hauptziel dieses Artikels ist es, am Ende eine kleine, unkomplizierte Angular-Anwendung zu haben, die ein soziales Netzwerk simuliert. In dieser Anwendung können bis zu drei Freunde hinzugefügt werden. Wir werden diesen Vorgang auch durch Integrationstests verifizieren. Da das Thema dennoch sehr umfangreich ist, habe ich es auf zwei Teile aufgeteilt. In diesem hier werden die Grundlagen von Angular und die Implementierung der Anwendung behandelt und im zweiten Teil wollen wir unsere Anwendung dann durch Unit- und Integrationstests testen.


Repository zum Mitarbeiten

Zum Nachverfolgen der Schritte und für den Zugriff auf die im Projekt genutzten Bilddateien benötigst du das zugehörige Repository. Du kannst es mit Git (oder GitHub Desktop) über den Link in den Quellenangaben klonen. Zum selbstständigen Erarbeiten der Schritte empfehle ich dir, zunächst nur die Bilddateien herunterzuladen und sie in den Assets-Ordner (siehe Ordnerstruktur in Angular) zu kopieren.


Einführung in Angular

Angular installieren

Starte nun bitte das Terminal. Bevor du Angular installierst, prüfe bitte sicherheitshalber noch mal die Versionen von Node.js und npm:

C:\Users\Robert>node -v
v18.17.0

C:\Users\Robert>npm -v
9.6.7

Sollte eine Fehlermeldung auftreten, zum Beispiel:

C:\User\Robert>node -v
'node' is not recognized as an internal or external command, operable program or batch file.

C:\User\Robert>npm -v
'npm' is not recognized as an internal or external command, operable program or batch file.

Dann prüfe bitte den Installationspfad von Node.js und die Umgebungsvariable PATH. Falls erforderlich, lade Node.js erneut herunter und installiere es erneut. Der Paketmanager npm wird dabei automatisch mitinstalliert.

Wenn Node.js und npm installiert sind, kannst du nun auch Angular installieren:

C:\Users\Robert>npm install -g @angular/cli

Neben Angular werden automatisch auch noch der Testrunner Karma und das Testframework Jasmine installiert. Dazu kommen wir später. Nach der Installation von Angular steht dir nun der Befehl ng zur Verfügung und du kannst ein neues Projekt anlegen:

C:\User\Robert>ng new angular-demo-application
? Would you like to add Angular routing? (y/N) y
? Which stylesheet format would you like to use?
  CSS
> SCSS [ https://sass-lang.com/documentation/syntax#scss ]
  Sass [ https://sass-lang.com/documentation/syntax#the-indented-syntax ]
  Less [ http://lesscss.org ]
...
CREATE angular-demo-application/... (... bytes)
...
| Installing packages (npm)...

  • Die Option für Angular-Routing ermöglicht dir das Hinzufügen von Unterseiten zur Anwendung. Dies hat jedoch nichts mit den eigentlichen Komponenten zu tun.
  • Die Stylesheet-Sprache kannst du nach Belieben wählen. Meine persönliche Empfehlung ist CSS oder (wie in diesem Projekt) SCSS. SCSS ist eine Erweiterung von CSS, die zusätzliche Funktionen wie Variablen, verschachtelte Selektoren und Mixins bietet. Dadurch wird das Styling deiner Anwendung effizienter und organisierter.

Ordnerstruktur in Angular

Bevor du überhaupt eine Zeile Code schreibst, ist es wichtig, dass du dich mit der Projektstruktur vertraut machst. Im Folgenden skizziere ich dir die grundlegende Ordnerstruktur, wobei ich mich auf diejenigen Ordner und Dateien konzentriere, die für diesen Blogbeitrag relevant sind.

angular-demo-application
├── dist
│   └── angular-demo-application
├── node_modules
├── src
│   ├── app
│   │   ├── example
│   │   │   ├── example.component.html
│   │   │   ├── example.component.scss
│   │   │   ├── example.component.spec.ts
│   │   │   └── example.component.ts
│   │   ├── app-routing.module.ts
│   │   ├── app.component.html
│   │   ├── app.component.scss
│   │   ├── app.component.spec.ts
│   │   ├── app.component.ts
│   │   └── app.module.ts
│   ├── assets
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   └── styles.scss
├── angular.json
├── package.json
└── tsconfig.json

Kurze Erklärung:

  • angular-demo-application: Jedes Angular-Projekt befindet sich in einem eigenen Ordner, wie du es sicher von anderen Entwicklungsumgebungen kennst.
  • dist/angular-demo-application: Hierhin wird die Anwendung kompiliert. Alle benötigten Source-Files werden hineinkopiert, und die TypeScript-Dateien werden in gewöhnliches JavaScript transpiliert.
  • node_modules: Dieser Ordner enthält die Abhängigkeiten und Pakete, die die Angular-Anwendung benötigt. Diese werden normalerweise automatisch von Angular und npm verwaltet.
  • src: Der Hauptordner deiner Angular-Anwendung, in dem der Großteil des Quellcodes und der Ressourcen enthalten sind.
  • app: Dies ist der Kern der Anwendung. Hier findest du alle Angular-Komponenten, Module, Services und andere wichtige Teile.
  • example: Ein Beispielordner für eine Komponente. Eine Komponente besteht in der Regel aus vier Dateien:
    • example.component.html: Enthält die grobe Struktur der Komponente als HTML-Code.
    • example.component.scss: Enthält die Stylesheet-Informationen in CSS bzw. SCSS.
    • example.component.spec.ts: Enthält die Unittests in TypeScript für die Komponente.
    • example.component.ts: Enthält die Grundlogik der Komponente in TypeScript.
  • app-routing.module.ts: Hier werden die Unterseiten der Anwendung verwaltet. In diesem Blogbeitrag gehe ich nicht näher darauf ein, da wir nur eine Single-Page-Anwendung haben.
  • app.component.html: Die Hauptkomponente der Anwendung. Hier können andere Komponenten eingebettet werden.
  • app.component.scss: Die globale Stylesheet-Datei für die gesamte Anwendung.
  • app.component.spec.ts: Unittests in TypeScript für die Hauptkomponente.
  • app.component.ts: Die Grundlogik der Hauptkomponente.
  • app.module.ts: Das Hauptmodul der Anwendung, das alle Komponenten, Dienste und andere Module zusammenführt.
  • assets: Hier werden statische Dateien, z.B. Bilder, Schriftarten usw. abgelegt, die unsere Anwendung benötigt.
  • favicon.ico: Das Icon der Anwendung, das in der Browser-Registerkarte angezeigt wird.
  • index.html: Die Einstiegs-HTML-Datei für die Anwendung. Hier wird normalerweise das HTML-Tag <app-root> platziert, das die Hauptkomponente einfügt.
  • main.ts: Die Einstiegsdatei für deine Anwendung, in der die Anwendung gestartet wird.
  • styles.scss: Die globale Stylesheet-Datei für die gesamte Anwendung, die auf alle Komponenten angewendet wird.
  • angular.json: Die Konfigurationsdatei des Angular-Projekts, in der verschiedene Einstellungen wie Build-Optionen, Erweiterungen usw. festgelegt werden
  • package.json: Die Datei, die die Metadaten und Abhängigkeiten des Projekts enthält. Hier werden die benötigten Pakete und ihre Versionen aufgeführt.
  • tsconfig.json: Die TypeScript-Konfigurationsdatei, die Einstellungen für den TypeScript-Compiler enthält.

Angular-Anwendung starten

Ist der Start der Anwendung zum jetzigen Zeitpunkt überraschend für dich? Schließlich hast du ja noch gar keinen eigenen Code geschrieben, oder? Ja, das ist richtig. Aber Angular erstellt mit dem Befehl ng new automatisch eine Art Demo-Anwendung. Wenn wir uns die index.html ansehen, steht da lediglich Folgendes drin:

<!doctype html>
<html>
        <head>
                <meta charset="utf-8">
                <title>AngularDemoApplication</title>
                <base href="/">
                <meta name="viewport" content="width=device-width, initial-scale=1">
                <link rel="icon" type="image/x-icon" href="favicon.ico">
        </head>
        <body>
                <app-root></app-root>
        </body>
</html>

Die soll auch so bleiben, denn die eigentliche Anwendung wird durch die beiden Tags <app-root></app-root> eingebettet. Die eingebettete HTML-Datei dazu ist die app.component.html. In der zugehörigen TypeScript-Datei sieht man, dass sie das Tag <app-root> hat:

import { Component, OnInit } from "@angular/core";

@Component({
        selector: "app-root",
        templateUrl: "./app.component.html",
        styleUrls: ["./app.component.scss"]
})
export class AppComponent implements OnInit {
        title = "angular-demo-application";

        ngOnInit() {}
}

Der Dekorator @Component beschreibt die Komponente genauer. Dort siehst du folgende drei Eigenschaften:

  • selector: Hier steht String, mit dem du bestimmen kannst, wie das HTML-Tag heißen soll, mit dem du auf deine Komponenten zugreifst. In diesem Blog halten wir uns an die Default-Konvention, dass vor den Komponentennamen im HTML das Wort app mit Bindestrich steht.
  • templateUrl: Hier steht, zu welcher HTML-Datei das TypeScript, das die Logik implementiert, gehört.
  • styleUrls: Hierbei handelt es sich um ein Array, in welchem alle (S)CSS-Dateien angegeben sind, die das Design der HTML-Komponente beschreiben.
Um die Anwendung zu starten, verwende den Befehl ng serve. Wenn du Visual Studio Code verwendest, empfehle ich dir, das Terminal innerhalb der IDE zu verwenden. Dadurch erkennt die IDE, wenn du Code bearbeitest und speicherst, und kompiliert deine Anwendung automatisch nach jedem Speichervorgang. Das ermöglicht dir eine „Live“-Entwicklung deiner Anwendung. Falls du jedoch ein extra Terminal verwendest, kannst du deine Anwendung auch manuell mit ng build kompilieren lassen. Wichtig ist außerdem, dass du dich stets im Angular-Projektordner befindest. Sollte dies nicht der Fall sein, quittiert Angular dies mit der folgenden Fehlermeldung:
C:\Users\Robert\>ng serve
The serve command requires to be run in an Angular project, but a project definition could not be found.
Falls dies du also nicht in deinem Projektordner bist, wechsle dort hinein, beispielsweise mit:
C:\Users\Robert\>cd angular-demo-application
C:\Users\Robert\angular-demo-application\>
Und dann steht dem Start nichts mehr im Wege:
C:\Users\Robert\angular-demo-application>ng serve --open
√ Browser application bundle generation complete.

Initial Chunk Files | Names | Raw Size
vendor.js | vendor | 2.33 MB |
polyfills.js | polyfills | 333.21 kB |
styles.css, styles.js | styles | 230.94 kB |
main.js | main | 48.15 kB |
runtime.js | runtime | 6.54 kB |

| Initial Total | 2.94 MB

Build at: 2023-08-22T14:03:13.256Z - Hash: 56ce367093804d17 - Time: 5318ms

** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **


√ Compiled successfully.
√ Browser application bundle generation complete.

Initial Chunk Files | Names | Raw Size
main.js | main | 48.15 kB |
runtime.js | runtime | 6.54 kB |

3 unchanged chunks

Build at: 2023-08-22T14:15:48.519Z - Hash: 637f1efa8a1a1564 - Time: 1340ms

√ Compiled successfully.

Die Befehlsoption --open signalisiert Angular, dass die Anwendung im Standardbrowser geöffnet werden soll. Wenn du die Option weglässt, kannst du die Anwendung nach dem Kompilieren auch manuell öffnen, indem du in der Adresszeile deines Browsers die URL localhost:4200 eingibst und mit der Eingabetaste bestätigst. Jedenfalls sollte das Ganze nun so aussehen (Klicken zum Vergrößern):


Komponenten in Angular erstellen

Angular-Anwendungen bestehen ja hauptsächlich aus Komponenten und Diensten. Zuerst wollen wir also eine neue Komponente erstellen. Wir nennen diese header. Um sie zu erstellen, gibt es den Befehl ng generate component. Also generiere bitte deine erste, eigene Komponente. Dazu kannst du einfach ein weiteres Terminal öffnen. Wichtig ist nur, dass du im Angular-Projektordner bist.

C:\Users\Robert\angular-demo-application>ng generate component header
CREATE src/app/header/header.component.html (21 bytes)
CREATE src/app/header/header.component.spec.ts (559 bytes)
CREATE src/app/header/header.component.ts (203 bytes)
CREATE src/app/header/header.component.scss (0 bytes)
UPDATE src/app/app.module.ts (475 bytes)

Jetzt haben wir einen neuen Ordner in unserer Projektstruktur:

angular-demo-application
├── src
│   ├── app
│   │   ├── header
│   │   │   ├── header.component.html
│   │   │   ├── header.component.scss
│   │   │   ├── header.component.spec.ts
│   │   │   └── header.component.ts
│   │   ├── app-routing.module.ts
│   │   ├── app.component.html
│   │   ├── app.component.scss
│   │   ├── app.component.spec.ts
│   │   ├── app.component.ts
│   │   └── app.module.ts
[...]

Wie erwartet hat Angular für uns einen Unterordner für die Header-Komponente mit den vier benötigten Dateien erstellt. Zuerst wollen wir uns um die HTML-Datei kümmern. Standardmäßig fügt er eine Art Default-Code ein, in diesem Fall:

<p>header works!</p>

Ersetz den kompletten Code in der header.component.html bitte durch folgenden:

<header>
        <img src="assets/images/simplytest.png"/>
</header>

Falls du das Beispiel selbst ausprobierst, ist es spätestens jetzt wichtig, dass du die Bilder in den Assets-Ordner geladen hast, damit die Angular-Anwendung richtig dargestellt wird. Wir wollen das ganze auch ein bisschen schön formatieren. Deswegen trag in die Datei header.component.scss bitte Folgendes ein:

header {
        background-color: lightgray;
        box-shadow: 8px 8px 8px rgba(0, 0, 0, 0.5);
}

Und damit wir den Header auch sehen, ersetzt einfach alles aus der app.component.html erst einmal durch folgenden Code:

<app-header/>
Das wird dann später noch erweitert. Das ganze wollen wir mit einer Hintergrundfarbe versehen, ich habe eine Art helles Gelb-Grau genommen. Du darfst gerne auch mit anderen Farben experimentieren. Trag bitte Folgendes in die app.component.scss ein:
html, body {
        height: 100%;
}

body {
        background-color: rgba(255, 255, 222, 0.75);
        font-family: Arial, Helvetica, sans-serif;
        margin: 0;
}

.default-card {
        background-color: #123456;
        border-radius: 10px;
        box-shadow: rgba(0, 0, 0, 0.5);
        box-sizing: border-box;
        margin: 15px;
        margin-bottom: 10px;
        width: 480px;
}

.margin-bottom-15 {
        margin-bottom: 15px;
}

.padding-15 {
        padding: 15px;
}

Die CSS-Klassen .default-card, .margin-bottom-15 und .padding-15 brauchen wir später noch. Es kommen ja noch weitere Komponenten dazu. Wenn du alles richtig gemacht hast, sollte deine Angular-Anwendung nun wie folgt aussehen (Klicken zum Vergrößern):

Du kannst in der HTML-Datei der Komponente wie gewohnt jeglichen HTML-Code einfügen, um die Struktur genauer zu beschreiben. Selbstverständlich kannst du auch andere Komponenten, die du bereits erstellt hast, dort einbetten. Das Gleiche haben wir ja hier in der Hauptkomponente app.component.html gemacht. Und genauso kannst du die Style-Informationen in der entsprechenden (S)CSS-Datei deiner Komponente anpassen, die dann hierarchisch nur für die Komponente und darunter liegende Elemente gelten.


Quellcode der Komponenten

Schauen wir uns kurz die beiden TypeScript-Dateien an. Dort sollte nur wenig Code drinstehen, der in etwa wie folgt aussehen sollte:

header.component.ts:

import { Component, OnInit } from "@angular/core";

@Component({
        selector: "app-header",
        templateUrl: "./header.component.html",
        styleUrls: ["./header.component.scss"]
})
export class HeaderComponent implements OnInit {
        ngOnInit() {}
}

header.component.spec.ts:

import { ComponentFixture, TestBed } from "@angular/core/testing";
import { HeaderComponent } from "./header.component";

describe("HeaderComponent", () => {
        let component: HeaderComponent;
        let fixture: ComponentFixture<HeaderComponent>;

        beforeEach(() => {
                TestBed.configureTestingModule ({
                        imports: [],
                        declarations: [HeaderComponent]
                }).compileComponents();
                fixture = TestBed.createComponent(HeaderComponent);
                component = fixture.componentInstance;
                fixture.detectChanges();
        });

        it("should be created", () => {
                expect(component).toBeTruthy();
        });
});

Falls sie komplett anders aussehen, bspw. könnte es sein, dass im Testscript vorgefertigte Tests drinstehen, dann ersetze sie bitte durch obigen Code. Ersetze auch bitte die beiden TypeScript-Dateien der App-Komponente durch folgenden Code:

app.component.ts:

import { Component, OnInit } from "@angular/core";

@Component({
        selector: "app-root",
        templateUrl: "./app.component.html",
        styleUrls: ["./app.component.scss"]
})
export class AppComponent  implements OnInit {
        title = "angular-demo-application";

        ngOnInit() {}
}

app.component.spec.ts:

import { AppComponent } from "./app.component";
import { ComponentFixture, TestBed } from "@angular/core/testing";

describe("AppComponent", () => {
        let component: AppComponent;
        let fixture: ComponentFixture<AppComponent>;
      
        beforeEach(() => {
                TestBed.configureTestingModule({
                        imports: [],
                        declarations: [AppComponent]
                }).compileComponents();

                fixture = TestBed.createComponent(AppComponent);
                component = fixture.componentInstance;
                fixture.detectChanges();
        });

        it("should be created", () => {
                expect(component).toBeTruthy();
        });
});

Für diese Beispiel-Anwendung bitte ich dich, nach dem Erstellen der Komponente jeweils die beiden TypeScript-Dateien wie oben anzupassen. Ich gehe dann später noch auf die entsprechenden Komponenten-Tests ein.

Als Nächstes benötigen wir zusätzliche Komponenten, die in die Anwendung eingebettet werden sollen. Verwende den Befehl ng generate component, um die unten angegebenen vier Komponenten zu generieren. Achte dabei bitte auf die Schreibweise. Hier kannst du noch mal nachsehen, wie das geht.

  • friend-box: Diese Komponente repräsentiert eine Box, in der die Profile angezeigt werden, denen du folgst.
  • card: Diese Komponente stellt eine Profilkarte dar. Sie enthält ein Bild, darunter den Benutzernamen und eine Beschreibung sowie zwei Schaltflächen: „Like“ und „Message“.
  • profile-row: Hierbei handelt es sich um einzelne Profile, die in den Profilvorschlägen angezeigt werden.
  • proposals: Diese Komponente zeigt die zuvor erwähnten Profilvorschläge an.

Dienste in Angular erstellen

Bevor du die Komponenten selbst anpassen kannst, ist erst noch ein weiterer Schritt durchzuführen. Das Ziel ist es, auf unserem kleinen, sozialen Netzwerk Freunde hinzuzufügen. Die Komponenten FriendBox und ProfileRow müssen dabei miteinander kommunizieren können. Wie du es sicherlich aus anderen sozialen Netzwerken kennst, wenn du auf „Folgen“ klickst, sollte der „Folgen“-Link verschwinden oder deaktiviert werden. Schließlich kannst du einem anderen Account nicht zweimal folgen. Außerdem müssen die Profile in der Freundschaftsbox angezeigt werden. Diese Funktionalität kannst du mit einem Dienst realisieren. In Angular erstellst du einen Dienst mit dem Befehl ng generate service:
C:\Users\Robert\angular-demo-application>ng generate service add-friend
CREATE src/app/add-friend.service.spec.ts (373 bytes)
CREATE src/app/add-friend.service.ts (138 bytes)

C:\Users\Robert\angular-demo-application>
Im Gegensatz zu den Komponenten erzeugt Angular hier jetzt nur zwei Dateien, und zwar direkt im app-Verzeichnis: app-friend-service.spec.ts und app-friend-service.spec.ts. Normalerweise ist es vielleicht angenehmer, Dienste in einem speziellen Unterordner zu haben, insbesondere wenn mehrere Dienste vorhanden sind. Doch für unsere kleine Anwendung reicht es vorerst aus. Die erste TypeScript-Datei enthält wieder die Logik, während die zweite die Unittests beinhaltet. Hier ist der Inhalt des Spec-Files, ähnlich wie bei den Komponenten, aber mit einigen Anpassungen:
import { AddFriendService } from "./add-friend.service";
import { TestBed } from "@angular/core/testing";

describe("AddFriendService", () => {
        let service: AddFriendService;

        beforeEach(() => {
                TestBed.configureTestingModule({});
                service = TestBed.inject(AddFriendService);
        });

        it("should be created", () => {
                expect(service).toBeTruthy();
        });
});

Der Hauptunterschied liegt darin, dass du keine Komponente hast, die mit der Methode createComponent() erzeugt wird. Stattdessen hast du einen Dienst, der mit inject() injiziert wird. Die Datei app-friend-service.ts sollte momentan in etwa so aussehen:

import { Injectable } from "@angular/core";

@Injectable({
        providedIn: "root"
})
export class AddFriendService {
        constructor() { }
}

Im Gegensatz zu den Komponenten steht hier der Dekorator @Injectable, da es sich, wie bereits erwähnt, um einen Dienst handelt. Die Eigenschaft providedIn gibt an, wo der Dienst zur Verfügung steht:

  • „any“: Der Dienst steht der gesamten Anwendung zur Verfügung, und es können auch mehrere Dienstinstanzen vorhanden sein.
  • „root“: Der Dienst steht der gesamten Anwendung zur Verfügung, wobei es nur eine Dienstinstanz gibt – ähnlich einer statischen Klasse.
  • <ModulName>: Wenn du einen Modulnamen als Bezeichner angibst, steht der Dienst nur diesem Modul zur Verfügung.
  • <ServiceName>: Wenn du einen Dienstnamen als Bezeichner angibst, steht der Dienst nur einem anderen Dienst zur Verfügung.

Implementierung des Freundschaftsdienstes

Jetzt wollen wir die Logik des Dienstes implementieren. Er ist dafür verantwortlich, dass du in der Anwendung Freunde hinzufügen kannst:

import { Injectable } from "@angular/core";

@Injectable ({
        providedIn: "root"
})
export class AddFriendService {
        names: string[] = [];
        texts: string[] = [];
        images: number[] = [];

        getImage(index: number) {
                return `/assets/images/cards/${this.images[index]}.webp`;
        }

        addFriend(name: string, text: string, id: number): number {
                if(this.names.length >= 3)
                        return 400;

                this.names.push(name);
                this.texts.push(text);
                this.images.push(id);
                return 200;
        }

        constructor() { }
}

Der Dienst enthält drei Arrays: names, texts und images, in denen die Informationen der Freunde gespeichert sind. Die Methode getImage() verwendet den Index, um den vollständigen Dateinamen mit relativem Pfad abzurufen. Wenn du die Beispiele selbst durchgehst, achte bitte darauf, die Bilddateien korrekt in den Assets-Ordner zu platzieren. Andernfalls könnten sie nicht angezeigt werden oder Fehlermeldungen auftreten. Die Bilder befinden sich im Unterordner assets/images, während die Profilbilder in assets/images/cards zu finden sind. Der Service wird durch die Methode addFriend() aufgerufen, um die entsprechenden Informationen zu den Arrays hinzuzufügen. Ich habe mir überlegt, dass der Service einen Code im Stil eines HTTP-Statuscodes zurückgibt. Vor dem Hinzufügen überprüft er die Array-Eigenschaft length, um festzustellen, ob die maximale Anzahl erreicht wurde. In diesem Fall wird der Fehlercode 400 zurückgegeben. Andernfalls werden die Profilinformationen hinzugefügt, und der Code 200 wird zurückgegeben.


Angular-Spezialitäten

In diesem Beitrag hast du bisher Komponenten als statischen HTML-Code erzeugt. Doch eine der Stärken von Angular liegt darin, Komponenten dynamisch zu erzeugen. Das bedeutet, dass du Komponenten bspw. basierend auf bestimmten Bedingungen anzeigen lassen kannst. Zwei besonders nützliche Funktionen, auf die ich eingehen werde, sind ngIf und ngFor.

Bedingte Anzeige mit „ngIf“

Das Kürzel „ngIf“ steht im Wesentlichen für „Angular-If“ und ermöglicht es, eine Komponente nur dann darzustellen, wenn eine bestimmte Bedingung erfüllt ist. Diese Bedingung kann eine einfache boolesche Variable sein, die entweder true oder false ist. Alternativ kannst du auch eine komplexere Bedingung verwenden. Hierbei können die Variablen auf Eigenschaften zugreifen, auf die die Komponente direkt zugreift. Lass uns jetzt ngIf in der ProfileRowComponent verwenden. Dafür benötigen wir eine kleine Erweiterung im Code. Bitte passe die Datei profile-row.component.ts wie folgt an:
import { AddFriendService } from "../add-friend.service";
import { Component, Input, OnInit } from "@angular/core";

@Component ({
        selector: "app-profile-row",
        templateUrl: "./profile-row.component.html",
        styleUrls: ["./profile-row.component.scss"]
})
export class ProfileRowComponent implements OnInit {
        @Input()
        id: number = 0;

        @Input()
        name: string = "";

        @Input()
        description: string = "";

        @Input()
        canFollow: boolean = true;

        addFriend(name: string, description: string, id: number) {
                let result = this.friendService.addFriend(name, description, id);
                if(result == 200)
                        this.canFollow = false;
                else if(result == 400)
                        alert("Du kannst nicht mehr als 3 Freunde haben.");
                else
                        alert("Unerwarteter Fehler");
        }

        constructor(public friendService: AddFriendService) {}

        ngOnInit(): void {}

        with(object: any): ProfileRowComponent {
                let profile = object as ProfileRowComponent;
                this.id = profile.id;
                this.name = profile.name;
                this.description = profile.description;
                this.canFollow = profile.canFollow;
                return this;
        }
}
Das Flag canFollow dient hierbei als Bedingung. Der Dekorator @Input() ermöglicht es, auf diese Variablen im HTML-Code deiner Angular-Anwendung ähnlich wie auf Attribute zuzugreifen. Die Methode addFriend() führt den Dienst AddFriendService aus und verarbeitet das Ergebnis. Und jetzt kannst du auch die zugehörige HTML-Datei profile-row.component.html anpassen:
<div class="profile-row margin-bottom-15">
        <img src="/assets/images/cards/{{id}}.webp"/>
        <div class="name">
                <b>{{name}}</b><br/>
                <span class="description">{{description}}</span>
        </div>
        <a id="follow" *ngIf="canFollow" (click)="addFriend(name, description, id)">Folgen</a>
</div>

Das angezeigte Bild in dieser Komponente hängt von der jeweiligen ID ab. Die Stringwerte der Attribute, in diesem Fall src, enthalten die Variable id in doppelten, geschweiften Klammern. Dasselbe Prinzip gilt auch für den Profilnamen und die Beschreibung weiter unten. Jetzt kommt die eigentliche Besonderheit: Mit *ngIf gibst du die Bedingung in einem String an, in unserem Fall "canFollow". Das sagt Angular: „Zeige diese Komponente nur an, wenn die Bedingung erfüllt ist“. Zusätzlich verfügt Angular über eigene Ereignisse, auf die es reagiert. In diesem Fall wird die Methode addFriend() aufgerufen, wenn du auf „Folgen“ klickst. Während man in normalem HTML onclick verwenden würde, verwendet Angular das Ereignis (click).


Dynamisches Generieren mit „ngFor“

Die zweite Besonderheit in Angular, die ich hier hervorheben möchte, ist die Fähigkeit, dynamisch mehrere Komponenten zu erzeugen, ohne die genaue Anzahl im Voraus zu kennen. Dies ähnelt einer Schleife und wird durch *ngFor ermöglicht, was für „Angular-For“ steht. Lass uns auch hier den Code entsprechend anpassen. Zuerst die Datei proposals.component.ts:

import { Component, OnInit } from "@angular/core";

@Component({
        selector: "app-proposals",
        templateUrl: "./proposals.component.html",
        styleUrls: ["./proposals.component.scss"]
})
export class ProposalsComponent implements OnInit {
        names: string[] = [
                "First User",
                "Second User",
                "Third User",
                "Fourth User",
                "Fifth User",
                "Sixth User"
        ];

        texts = [
                "This is a card.",
                "That is also a card.",
                "This is anonther card.",
                "That is the fourth card. ",
                "This is the fifth card.",
                "That is the last card."
        ];

        images: number[] = [0, 1, 2, 3, 4, 5];

        constructor() {}

        ngOnInit() {}

        get(index: number) {
                return {
                        name: this.names[index],
                        text: this.texts[index],
                        id: this.images[index]
                };
        }
}
Die Benutzernamen und Beschreibungen haben hier keine tiefere Bedeutung. Du kannst hier auch eigene, beliebige Bezeichnungen einsetzen. Als Nächstes die zugehörige HTML-Datei profile-row.component.html:
<app-profile-row [canFollow]="false" [id]="0" [name]="names[0]" [description]="texts[0]"/>
<div class="margin-bottom-15">
        <b>Freundesvorschl&auml;ge</b>
</div>
<app-profile-row
        *ngFor="let i of [1, 2, 3, 4, 5]"
        [id]="i"
        [name]="names[i]"
        [description]="texts[i]">
</app-profile-row>
Eine weitere bemerkenswerte Eigenschaft zeigt sich in der ersten Zeile des Codes. Hier wird deutlich, dass du auch direkt im HTML-Code die Eigenschaften der Komponentenklasse festlegen kannst. In diesem Fall wird das erste Profil mit „deinem“ Profil gefüllt und durch [canFollow]="false" wird sichergestellt, dass du dir selbst nicht folgen kannst. Die Attribute, die du ändern möchtest, werden dabei in eckige Klammern gesetzt. Und dort, wo die Komponente ProfileRow mit dem Tag <app-profile-row> eingebettet wird, kommt jetzt *ngFor zum Einsatz. Im String "let i of [1, 2, 3, 4, 5]" steht genau das Gleiche drin, was du beispielsweise auch in TypeScript schreiben würdest, in der Form for(let i of [1, 2, 3, 4, 5]).

Implementierung der restlichen Komponenten

Wenn du bis hierhin aktiv mitgemacht hast, lade dir gerne den restlichen Teil des Repositorys herunter, da dieselben Prinzipien auch bei den restlichen Komponenten angewandt werden. Darin sind nämlich noch die angepassten SCSS-Dateien enthalten, um der Anwendung ein ansprechendes Design zu verleihen. Außerdem befinden sich in der Anwendung zwei Icons aus dem Material-Design. Dazu installiere bitte folgendes Package:
C:\Users\Robert\angular-demo-application>ng add @angular/material
ℹ Using package manager: npm
✔ Found compatible package version: 
✔ Package information loaded.

The package  will be installed and executed.
Would you like to proceed? Yes
✔ Packages successfully installed.
? Choose a prebuilt theme name, or "custom" for a custom theme: (Use arrow keys)
> Indigo/Pink [ Preview: https://material.angular.io?theme=indigo-pink ]
  Deep Purple/Amber [ Preview: https://material.angular.io?theme=deeppurple-amber ]
  Pink/Blue Grey [ Preview: https://material.angular.io?theme=pink-bluegrey ]
  Purple/Green [ Preview: https://material.angular.io?theme=purple-green ]
? Set up global Angular Material typography styles? No
? Include the Angular animations module? Do not include
UPDATE package.json (1121 bytes)
✔ Packages installed successfully.
UPDATE angular.json (3098 bytes)
UPDATE src/index.html (604 bytes)
UPDATE src/styles.scss (510 bytes)
Außerdem ist es notwendig, dass du die Datei app.module.ts wie folgt anpasst:
import { AppComponent } from "./app.component";
import { AppRoutingModule } from "./app-routing.module";
import { BrowserModule } from "@angular/platform-browser";
import { CardComponent } from "./card/card.component";
import { FriendBoxComponent } from "./friend-box/friend-box.component";
import { HeaderComponent } from "./header/header.component";
import { MatIconModule } from "@angular/material/icon";
import { NgModule } from "@angular/core";
import { ProfileRowComponent } from "./profile-row/profile-row.component";
import { ProposalsComponent } from "./proposals/proposals.component";

@NgModule({
        declarations: [
                AppComponent,
                CardComponent,
                FriendBoxComponent,
                HeaderComponent,
                ProfileRowComponent,
                ProposalsComponent
        ],
        imports: [
                AppRoutingModule,
                BrowserModule,
                MatIconModule
        ],
        providers: [],
        bootstrap: [AppComponent]
})
export class AppModule { }

Wenn du das Repository erfolgreich geklont oder heruntergeladen hast und alle notwendigen Bibliotheken installiert sind, sollte die Anwendung jetzt wie folgt aussehen (Klicken zum Vergrößern):


Testen der Komponenten

Nun, da du die grundlegende Mini-Anwendung implementiert hast, ist es an der Zeit, sie zu testen. Dazu lade ich dich auf meinen zweiten Teil Einführung in Angular-Komponenten (Part 2) ein.

Quellenangaben


1 Kommentar

Trackbacks & Pingbacks

  1. […] in Angular-Komponenten. Falls du dir vorher die Grundlagen erarbeiten möchtest, sieh dir gerne Einführung in Angular-Komponenten (Part 1) […]

Hinterlasse einen Kommentar

An der Diskussion beteiligen?
Hinterlasse uns deinen Kommentar!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert