Bevor wir in die Theorie eintauchen, lassen Sie uns direkt eine einfache Angular-Komponente erstellen. Öffnen Sie Ihr Terminal und führen Sie folgende Befehle aus:
ng new my-first-app
cd my-first-app
ng generate component greetingDer erste Befehl ng new my-first-app erstellt ein
vollständiges Angular-Projekt mit allen notwendigen
Konfigurationsdateien und einer Basisstruktur. Der letzte Befehl
ng generate component greeting ist besonders wichtig: Er
nutzt den Angular CLI (Command Line Interface), um automatisch eine neue
Komponente mit dem Namen greeting zu erstellen. Dabei
werden vier Dateien generiert, die zusammen eine funktionsfähige
Komponente bilden.
Schauen wir uns an, was Angular für uns generiert hat:
Diese Dateien bilden das Grundgerüst jeder Angular-Komponente: - Die TypeScript-Datei (.ts) enthält die Logik - Die HTML-Datei definiert die Struktur - Die CSS-Datei bestimmt das Aussehen - Die Spec-Datei (.spec.ts) ist für Tests vorgesehen
Öffnen wir die Hauptdatei greeting.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-greeting',
templateUrl: './greeting.component.html',
styleUrls: ['./greeting.component.css']
})
export class GreetingComponent {
// Hier kommt unsere Komponenten-Logik hin
}Die @Component-Dekorator-Funktion ist ein spezielles
TypeScript-Feature, das der Klasse Metadaten hinzufügt. Diese Metadaten
teilen Angular mit, wie die Komponente verwendet werden soll: -
selector definiert den HTML-Tag, mit dem wir die Komponente
später einbinden (wie ein eigenes HTML-Element) -
templateUrl verweist auf die separate HTML-Datei, die die
Struktur der Komponente beschreibt - styleUrls gibt an,
welche CSS-Dateien für das Styling dieser speziellen Komponente
verwendet werden sollen
Bearbeiten wir die Komponente, um sie interaktiver zu gestalten:
export class GreetingComponent {
name: string = 'Welt';
changeName() {
this.name = 'Angular-Entwickler';
}
}Hier fügen wir zwei wichtige Elemente hinzu: - Eine Eigenschaft
name vom Typ String mit dem Ausgangswert ‘Welt’ - Eine
Methode changeName(), die den Wert dieser Eigenschaft
ändert Diese Elemente bilden das Datenmodell und die Verhaltenslogik der
Komponente.
greeting.component.html:<div class="greeting-card">
<h2>Hallo, {{name}}!</h2>
<button (click)="changeName()">Ändere Namen</button>
</div>Das Template verwendet zwei grundlegende Konzepte der
Angular-Template-Syntax: - Interpolation mit
{{name}}: Diese doppelten geschweiften Klammern binden den
Wert der name-Eigenschaft aus der Komponenten-Klasse in das
Template ein. Wenn sich der Wert ändert, aktualisiert Angular
automatisch die Anzeige. - Event-Binding mit
(click)="changeName()": Diese Syntax mit runden Klammern
verbindet das Klick-Ereignis des Buttons mit der
changeName()-Methode unserer Komponente. Bei jedem Klick
wird die Methode ausgeführt.
greeting.component.css
hinzu:.greeting-card {
border: 2px solid #1976d2;
border-radius: 8px;
padding: 20px;
margin: 20px;
text-align: center;
background-color: #f5f5f5;
}
button {
background-color: #1976d2;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}Das Besondere an diesem CSS: Angular kapselt diese Stile automatisch, sodass sie nur für diese spezifische Komponente gelten und nicht den Rest der Anwendung beeinflussen. Dies ist Teil des Shadow DOM-Konzepts, das Angular verwendet, um Komponenten zu isolieren und unerwünschte Stil-Überschneidungen zu vermeiden.
app.component.html und ersetzen Sie den vorhandenen Inhalt
durch:<div class="content">
<h1>Willkommen zur Angular-Komponenten Demo</h1>
<app-greeting></app-greeting>
</div>Hier verwenden wir den im Selektor definierten Tag
<app-greeting>. Angular erkennt diesen Tag und
ersetzt ihn durch den gerenderten Inhalt unserer Komponente. Dies zeigt
die Wiederverwendbarkeit von Komponenten – sie können wie eigene
HTML-Elemente in beliebigen Templates eingesetzt werden.
Starten Sie die Anwendung mit ng serve und öffnen Sie
http://localhost:4200 in Ihrem Browser. Sie haben gerade Ihre erste
interaktive Angular-Komponente erstellt!
Was haben wir gerade getan? Lassen Sie uns die Hauptbestandteile unserer Komponente im Detail analysieren:
selector: Der HTML-Tag, mit dem wir die Komponente
verwenden (wie <app-greeting>)templateUrl: Der Pfad zum HTML-Template, das die
Struktur definiertstyleUrls: Die CSS-Dateien für das Styling, die
gekapselt nur für diese Komponente geltenname bilden das Datenmodell der
KomponentechangeName() definieren das Verhalten und
können auf Ereignisse reagieren{{name}} zeigt den Wert der
name-Eigenschaft dynamisch an(click)="changeName()" verbindet
DOM-Ereignisse mit Methoden der Komponente[property]="value" oder Two-Way-Binding
[(ngModel)]="property"Ein entscheidender Aspekt zum Verständnis von Angular-Komponenten ist der Datenfluss:
name = 'Welt'
erstellt.changeName()-Methode ausgeführt.name-Eigenschaft auf
‘Angular-Entwickler’.Dies demonstriert das reaktive Verhalten von Angular – Änderungen im Datenmodell führen automatisch zu Aktualisierungen in der Benutzeroberfläche. Sie müssen das DOM nicht manuell manipulieren, wie es bei älteren JavaScript-Frameworks oft nötig war.
Komponenten sind die grundlegenden Bausteine jeder Angular-Anwendung. Sie können sich eine Komponente wie einen wiederverwendbaren LEGO-Baustein vorstellen. Jede Komponente kapselt drei Dinge:
Diese Kapselung bietet mehrere Vorteile: - Wiederverwendbarkeit: Sie können Komponenten beliebig oft wiederverwenden. - Modularität: Komplexe Anwendungen können in überschaubare Teile zerlegt werden. - Testbarkeit: Komponenten können isoliert getestet werden. - Maintainability: Änderungen an einer Komponente betreffen nur diese, nicht die gesamte Anwendung.
Im Kern ist das Komponenten-basierte Design von Angular eine Umsetzung des Prinzips der Separation of Concerns – jede Komponente kümmert sich nur um einen bestimmten Aspekt der Anwendung.
Angular-Anwendungen bestehen typischerweise aus einer Hierarchie von
Komponenten. Die AppComponent ist die Wurzelkomponente, in
die weitere Komponenten wie unsere GreetingComponent
eingebunden werden. Diese Struktur kann beliebig tief sein, wobei
Komponenten miteinander kommunizieren können, indem sie Daten und
Ereignisse austauschen.
Diese hierarchische Struktur, kombiniert mit Angular’s Change Detection, ermöglicht es, komplexe interaktive Benutzeroberflächen zu erstellen, die effizient und performant sind. ## Datenfluss und Kommunikation
Erweitern wir unser Beispiel um eine Parent-Child Beziehung zwischen Komponenten. Erstellen wir eine neue Komponente:
ng generate component user-greetingBearbeiten wir die neue Komponente
user-greeting.component.ts:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-user-greeting',
templateUrl: './user-greeting.component.html',
styleUrls: ['./user-greeting.component.css']
})
export class UserGreetingComponent {
@Input() userName: string = 'Gast';
}Das Template in user-greeting.component.html:
<div class="user-card">
<h3>Persönliche Begrüßung für: {{userName}}</h3>
</div>Styling in user-greeting.component.css:
.user-card {
background-color: #e1f5fe;
border: 1px solid #03a9f4;
border-radius: 4px;
padding: 10px;
margin: 10px 0;
}Jetzt modifizieren wir unsere ursprüngliche
GreetingComponent, um die Child-Komponente zu
verwenden:
Aktualisieren Sie greeting.component.html:
<div class="greeting-card">
<h2>Hallo, {{name}}!</h2>
<button (click)="changeName()">Ändere Namen</button>
<div class="child-component">
<app-user-greeting [userName]="name"></app-user-greeting>
</div>
</div>Fügen Sie einen Stil für den Child-Container in
greeting.component.css hinzu:
.child-component {
margin-top: 20px;
padding: 10px;
background-color: #f9f9f9;
border-radius: 4px;
}Wir haben gerade: 1. Eine Child-Komponente erstellt 2. Die
@Input()-Dekorator verwendet, um Daten von der
Parent-Komponente zu empfangen 3. Die Child-Komponente in die
Parent-Komponente eingebettet 4. Daten von der Parent- zur
Child-Komponente über Property Binding [userName]="name"
übergeben
Jetzt wollen wir die Kommunikation in die andere Richtung ermöglichen - von der Child- zur Parent-Komponente:
Bearbeiten wir user-greeting.component.ts:
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-user-greeting',
templateUrl: './user-greeting.component.html',
styleUrls: ['./user-greeting.component.css']
})
export class UserGreetingComponent {
@Input() userName: string = 'Gast';
@Output() userNameChange = new EventEmitter<string>();
resetName() {
this.userNameChange.emit('Welt');
}
}Aktualisieren wir das Template in
user-greeting.component.html:
<div class="user-card">
<h3>Persönliche Begrüßung für: {{userName}}</h3>
<button (click)="resetName()">Zurücksetzen</button>
</div>Aktualisieren wir die Parent-Komponente
greeting.component.html, um auf das Event zu hören:
<div class="greeting-card">
<h2>Hallo, {{name}}!</h2>
<button (click)="changeName()">Ändere Namen</button>
<div class="child-component">
<app-user-greeting
[userName]="name"
(userNameChange)="name = $event">
</app-user-greeting>
</div>
</div>Was passiert hier: 1. Die Child-Komponente deklariert einen
EventEmitter mit @Output() 2. Bei einem
Ereignis (Button-Klick) emittiert die Child-Komponente einen Wert 3. Die
Parent-Komponente hört auf dieses Ereignis mit
(userNameChange)="name = $event" 4. Wenn das Ereignis
eintritt, wird die name-Variable der Parent-Komponente aktualisiert
Angular-Komponenten durchlaufen verschiedene Phasen während ihres Lebenszyklus. Wir können auf diese Phasen mit Lifecycle Hooks reagieren:
import {
Component, OnInit, OnChanges,
AfterViewInit, OnDestroy,
SimpleChanges, Input
} from '@angular/core';
@Component({
selector: 'app-lifecycle-demo',
template: `
<div class="lifecycle">
<h3>Lebenszyklus-Demo</h3>
<p>Status: {{status}}</p>
</div>
`,
styles: [`
.lifecycle {
border: 1px solid #673ab7;
padding: 10px;
background-color: #ede7f6;
margin: 10px 0;
}
`]
})
export class LifecycleDemoComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy {
@Input() data: string;
status: string = 'Initialisierung...';
constructor() {
console.log('Constructor aufgerufen');
this.status = 'Komponente konstruiert';
}
ngOnChanges(changes: SimpleChanges) {
console.log('OnChanges aufgerufen', changes);
this.status = 'Eingabe-Eigenschaften geändert';
}
ngOnInit() {
console.log('OnInit aufgerufen');
this.status = 'Komponente initialisiert';
}
ngAfterViewInit() {
console.log('AfterViewInit aufgerufen');
setTimeout(() => {
this.status = 'View initialisiert';
});
}
ngOnDestroy() {
console.log('OnDestroy aufgerufen');
// Aufräumarbeiten hier durchführen
}
}Die wichtigsten Lifecycle Hooks sind: - ngOnChanges: Wird aufgerufen, wenn sich eine @Input-Eigenschaft ändert - ngOnInit: Wird einmal aufgerufen, nachdem die Komponente initialisiert wurde - ngAfterViewInit: Wird aufgerufen, nachdem die View der Komponente initialisiert wurde - ngOnDestroy: Wird aufgerufen, bevor die Komponente zerstört wird
Content Projection (auch als Transklusion bekannt) ermöglicht es, HTML-Inhalte von außen in eine Komponente einzufügen:
Erstellen wir eine Container-Komponente:
ng generate component content-cardBearbeiten wir content-card.component.ts:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-content-card',
templateUrl: './content-card.component.html',
styleUrls: ['./content-card.component.css']
})
export class ContentCardComponent {
@Input() title: string = 'Karte';
}Das Template in content-card.component.html:
<div class="content-card">
<div class="card-header">
<h3>{{title}}</h3>
</div>
<div class="card-body">
<ng-content></ng-content>
</div>
</div>Styling in content-card.component.css:
.content-card {
border: 1px solid #ccc;
border-radius: 8px;
margin: 16px 0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.card-header {
background-color: #f5f5f5;
padding: 10px 16px;
border-bottom: 1px solid #ccc;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.card-body {
padding: 16px;
}Verwenden wir die Komponente in app.component.html:
<div class="content">
<h1>Angular-Komponenten Demo</h1>
<app-greeting></app-greeting>
<app-content-card title="Wichtige Information">
<p>Dies ist der projizierte Inhalt, der von außen in die Komponente eingefügt wird.</p>
<button>Aktion ausführen</button>
</app-content-card>
</div>Mit <ng-content></ng-content> können wir
HTML-Inhalte von außen in unsere Komponente “projizieren”. Dies ist
besonders nützlich für wiederverwendbare UI-Elemente wie Karten, Panels
oder Dialoge.
Wir können auch mehrere Projektionspunkte mit dem
select-Attribut definieren:
Aktualisieren wir content-card.component.html:
<div class="content-card">
<div class="card-header">
<h3>{{title}}</h3>
<div class="header-actions">
<ng-content select="[card-actions]"></ng-content>
</div>
</div>
<div class="card-body">
<ng-content></ng-content>
</div>
<div class="card-footer">
<ng-content select="[card-footer]"></ng-content>
</div>
</div>Erweitern wir unser CSS in
content-card.component.css:
.header-actions {
display: inline-block;
float: right;
}
.card-footer {
padding: 10px 16px;
background-color: #f5f5f5;
border-top: 1px solid #ccc;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}Verwenden wir die erweiterte Komponente in
app.component.html:
<app-content-card title="Erweiterte Karte">
<p>Dies ist der Hauptinhalt der Karte.</p>
<button card-actions>Bearbeiten</button>
<div card-footer>
Erstellt am: 19.03.2025
</div>
</app-content-card>Mit dem select-Attribut können wir gezielt Inhalte an
bestimmte Stellen in unserer Komponente projizieren: -
<ng-content> ohne select-Attribut:
Standard-Einfügepunkt -
<ng-content select="[card-actions]">: Projiziert
Elemente mit dem Attribut card-actions -
<ng-content select="[card-footer]">: Projiziert
Elemente mit dem Attribut card-footer
Eine gut durchdachte Komponenten-Hierarchie ist entscheidend für wartbare Angular-Anwendungen:
Angular bietet verschiedene Methoden zur Kapselung von Styles:
@Component({
selector: 'app-styling-demo',
template: `
<div class="demo">
<h3>Styling-Demo</h3>
<p>Dieser Text hat einen kapselunsabhängigen Stil.</p>
</div>
`,
styles: [`
.demo {
border: 2px solid #ff5722;
padding: 10px;
margin: 10px 0;
}
p {
color: #ff5722;
}
`],
encapsulation: ViewEncapsulation.Emulated // Standard
})
export class StylingDemoComponent { }Angular bietet drei Optionen für die Style-Kapselung: - ViewEncapsulation.Emulated: Standard, emuliert Shadow DOM-Kapselung - ViewEncapsulation.None: Keine Kapselung, Styles gelten global - ViewEncapsulation.ShadowDom: Verwendet natives Shadow DOM (nur moderne Browser)
Die emulierte Kapselung fügt automatisch einzigartige Attribute zu Elementen hinzu und transformiert CSS-Selektoren, sodass Styles komponenten-spezifisch bleiben.
Angular’s Change Detection sorgt dafür, dass die UI synchron mit dem Zustand der Anwendung bleibt:
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-performance-demo',
template: `
<div class="perf-demo">
<h3>Performance-optimierte Komponente</h3>
<p>Aktueller Wert: {{value}}</p>
<button (click)="increment()">Erhöhen</button>
</div>
`,
styles: [`
.perf-demo {
border: 2px dashed #9c27b0;
padding: 10px;
margin: 10px 0;
}
`],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PerformanceDemoComponent {
value = 0;
increment() {
this.value++;
}
}Mit ChangeDetectionStrategy.OnPush prüft Angular nur
dann auf Änderungen, wenn: - Eine @Input-Eigenschaft sich ändert
(Referenzvergleich) - Ein Event innerhalb der Komponente oder ihrer
Kinder ausgelöst wird - Ein Observable, das mit dem Async-Pipe verbunden
ist, einen neuen Wert emittiert
Dies kann die Performance erheblich verbessern, insbesondere bei großen Anwendungen mit vielen Komponenten.
Zum Abschluss erstellen wir eine komplexe Feature-Komponente, die verschiedene Konzepte kombiniert:
ng generate component todo-manager
ng generate component todo-list
ng generate component todo-item
ng generate component todo-formtodo-manager.component.ts:
import { Component } from '@angular/core';
interface Todo {
id: number;
title: string;
completed: boolean;
}
@Component({
selector: 'app-todo-manager',
templateUrl: './todo-manager.component.html',
styleUrls: ['./todo-manager.component.css']
})
export class TodoManagerComponent {
todos: Todo[] = [
{ id: 1, title: 'Angular Komponenten verstehen', completed: true },
{ id: 2, title: 'Komponenten-Kommunikation meistern', completed: false },
{ id: 3, title: 'Angular-Anwendung entwickeln', completed: false }
];
addTodo(title: string) {
if (title.trim()) {
const newId = Math.max(0, ...this.todos.map(t => t.id)) + 1;
this.todos.push({ id: newId, title, completed: false });
}
}
toggleTodo(id: number) {
this.todos = this.todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
);
}
deleteTodo(id: number) {
this.todos = this.todos.filter(todo => todo.id !== id);
}
}todo-manager.component.html:
<div class="todo-manager">
<h2>Aufgaben-Manager</h2>
<app-todo-form (addTodo)="addTodo($event)"></app-todo-form>
<app-todo-list
[todos]="todos"
(toggleTodo)="toggleTodo($event)"
(deleteTodo)="deleteTodo($event)">
</app-todo-list>
</div>todo-form.component.ts:
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-todo-form',
templateUrl: './todo-form.component.html',
styleUrls: ['./todo-form.component.css']
})
export class TodoFormComponent {
@Output() addTodo = new EventEmitter<string>();
newTodoTitle = '';
onSubmit() {
this.addTodo.emit(this.newTodoTitle);
this.newTodoTitle = '';
}
}todo-form.component.html:
<div class="todo-form">
<form (ngSubmit)="onSubmit()">
<input
type="text"
placeholder="Neue Aufgabe hinzufügen..."
[(ngModel)]="newTodoTitle"
name="todoTitle"
required>
<button type="submit" [disabled]="!newTodoTitle.trim()">Hinzufügen</button>
</form>
</div>todo-list.component.ts:
import { Component, Input, Output, EventEmitter } from '@angular/core';
interface Todo {
id: number;
title: string;
completed: boolean;
}
@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.component.html',
styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent {
@Input() todos: Todo[] = [];
@Output() toggleTodo = new EventEmitter<number>();
@Output() deleteTodo = new EventEmitter<number>();
}todo-list.component.html:
<div class="todo-list">
<div *ngIf="todos.length === 0" class="empty-state">
Keine Aufgaben vorhanden. Fügen Sie eine neue Aufgabe hinzu!
</div>
<app-todo-item
*ngFor="let todo of todos"
[todo]="todo"
(toggle)="toggleTodo.emit($event)"
(delete)="deleteTodo.emit($event)">
</app-todo-item>
</div>todo-item.component.ts:
import { Component, Input, Output, EventEmitter } from '@angular/core';
interface Todo {
id: number;
title: string;
completed: boolean;
}
@Component({
selector: 'app-todo-item',
templateUrl: './todo-item.component.html',
styleUrls: ['./todo-item.component.css']
})
export class TodoItemComponent {
@Input() todo: Todo;
@Output() toggle = new EventEmitter<number>();
@Output() delete = new EventEmitter<number>();
}todo-item.component.html:
<div class="todo-item" [class.completed]="todo.completed">
<label>
<input
type="checkbox"
[checked]="todo.completed"
(change)="toggle.emit(todo.id)">
{{ todo.title }}
</label>
<button (click)="delete.emit(todo.id)">Löschen</button>
</div>todo-item.component.css:
.todo-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
margin: 8px 0;
border: 1px solid #ddd;
border-radius: 4px;
background-color: white;
}
.todo-item.completed label {
text-decoration: line-through;
color: #888;
}
.todo-item button {
background-color: #f44336;
color: white;
border: none;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
}Integrieren Sie die Haupt-Komponente in
app.component.html:
<div class="content">
<h1>Angular-Komponenten in der Praxis</h1>
<app-todo-manager></app-todo-manager>
</div>Dieses Beispiel zeigt: 1. Eine hierarchische Komponentenstruktur 2. Datenkommunikation über @Input und @Output 3. Event-Handling und Zustandsverwaltung 4. Komponentenspezifisches Styling 5. Bedingte Rendering mit ngIf 6. Listenrendering mit ngFor
Angular-Komponenten sind die Grundbausteine moderner Web-Anwendungen. Durch die Aufteilung der Benutzeroberfläche in kleine, wiederverwendbare Komponenten können wir komplexe Anwendungen auf eine wartbare und verständliche Weise erstellen.
Die wichtigsten Punkte im Überblick:
Komponenten-Struktur: Jede Komponente besteht aus einer TypeScript-Klasse mit @Component-Dekorator, einem HTML-Template und optionalen CSS-Styles.
Komponenten-Kommunikation:
Lebenszyklus-Hooks: ngOnInit, ngOnChanges, ngAfterViewInit und andere ermöglichen Reaktionen auf verschiedene Phasen im Leben einer Komponente.
Content Projection: Mit ng-content können Inhalte von außen in eine Komponente projiziert werden.
Best Practices:
Durch das Meistern dieser Konzepte können Sie skalierbare, wartbare und performante Angular-Anwendungen entwickeln.