No history yet

Erweiterte Komponentenkommunikation

Effektive Komponentenkommunikation

In komplexen Angular-Anwendungen müssen Komponenten ständig miteinander kommunizieren. Daten fließen von Eltern zu Kindern, Ereignisse werden von Kindern an Eltern gesendet, und manchmal müssen Eltern direkt auf die Methoden ihrer Kinder zugreifen. Ein solides Verständnis dieser Kommunikationsmuster ist entscheidend für den Aufbau wartbarer und skalierbarer Anwendungen.

Komponenten sind die Bausteine von Angular-Anwendungen.

Aufbauend auf diesem Grundprinzip untersuchen wir nun die fortgeschrittenen Techniken, die diese Bausteine zu einem funktionierenden Ganzen verbinden.

Daten weitergeben mit @Input und @Output

Die grundlegendste Form der Kommunikation ist der Datenfluss zwischen einer Eltern- und einer Kindkomponente. Wie Sie bereits wissen, wird hierfür @Input verwendet, um Daten in eine Kindkomponente zu übergeben.

// user-profile.component.ts (Kindkomponente)
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-user-profile',
  template: `<p>Benutzername: {{ username }}</p>`
})
export class UserProfileComponent {
  @Input() username: string = '';
}

// app.component.html (Elternkomponente)
<app-user-profile [username]="'Anna'" />

Für die umgekehrte Richtung, von Kind zu Eltern, nutzen wir den @Output-Decorator in Kombination mit EventEmitter. Die Kindkomponente sendet keine Daten direkt, sondern stößt ein Ereignis an, auf das die Elternkomponente lauschen kann. So bleibt die Kopplung zwischen den Komponenten gering.

// vote-button.component.ts (Kindkomponente)
import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-vote-button',
  template: `<button (click)="vote()">Stimme ab!</button>`
})
export class VoteButtonComponent {
  @Output() voted = new EventEmitter<boolean>();

  vote() {
    this.voted.emit(true);
  }
}

// app.component.html (Elternkomponente)
<app-vote-button (voted)="onVoted($event)" />
<p *ngIf="hasVoted">Danke für deine Stimme!</p>

// app.component.ts (Elternkomponente)
export class AppComponent {
  hasVoted = false;

  onVoted(didVote: boolean) {
    this.hasVoted = didVote;
  }
}

Merke: @Input für Datenfluss von oben nach unten (Parent -> Child). @Output mit EventEmitter für Ereignisse von unten nach oben (Child -> Parent).

Flexible Inhalte mit Content Projection

Manchmal möchten wir eine Komponente als wiederverwendbaren Rahmen oder Container nutzen, dessen Inhalt von der Elternkomponente flexibel bereitgestellt wird. Hierfür gibt es die Content Projection.

Statt eines festen Templates in der Kindkomponente definieren wir mit dem Tag <ng-content> einen Platzhalter. Angular fügt an dieser Stelle den Inhalt ein, den wir in der Elternkomponente zwischen die Tags der Kindkomponente schreiben.

<!-- card.component.html (Kindkomponente) -->
<div class="card">
  <div class="card-body">
    <ng-content></ng-content>
  </div>
</div>

<!-- app.component.html (Elternkomponente) -->
<app-card>
  <h4>Ein Titel</h4>
  <p>Das ist ein Text, der in die Karte projiziert wird.</p>
</app-card>

Für noch mehr Kontrolle können wir mehrere Platzhalter verwenden, sogenannte Slots. Mithilfe des select-Attributs auf <ng-content> können wir festlegen, welcher Inhalt in welchen Slot projiziert wird. Die Auswahl kann über CSS-Selektoren wie Klassen, Attribute oder Element-Tags erfolgen.

<!-- panel.component.html (Kindkomponente mit Slots) -->
<div class="panel">
  <div class="panel-header">
    <ng-content select="[panel-title]"></ng-content>
  </div>
  <div class="panel-body">
    <ng-content select=".panel-content"></ng-content>
  </div>
</div>

<!-- app.component.html (Elternkomponente) -->
<app-panel>
  <h3 panel-title>Mein Panel</h3>
  <div class="panel-content">
    Hier steht der Hauptinhalt des Panels.
  </div>
</app-panel>

Direkter Zugriff auf Komponenten

Es gibt Situationen, in denen eine Elternkomponente direkt auf Eigenschaften oder Methoden einer Kindkomponente zugreifen muss. Dafür bietet Angular die Decorators @ViewChild und @ContentChild. Bevor wir diese verwenden können, müssen wir die Komponente oder das Element im Template mit einer Template-Referenzvariable markieren. Das geschieht einfach mit einem #-Symbol gefolgt von einem Namen.

Eine Template-Referenzvariable (#name) ist wie ein Spitzname für ein Element oder eine Komponente im HTML-Template, damit wir im Code darauf zugreifen können.

@ViewChild wird verwendet, um auf eine Komponente zuzugreifen, die sich im View Template der aktuellen Komponente befindet. Das heißt, die Kindkomponente wurde direkt im HTML der Elternkomponente platziert.

// timer.component.ts (Kindkomponente)
@Component({
  selector: 'app-timer',
  template: `<p>Verbleibende Zeit: {{ seconds }}s</p>`
})
export class TimerComponent {
  seconds = 10;
  // Methode, die von außen aufgerufen werden kann
  public start() {
    // ... Logik zum Starten des Timers
  }
}

// app.component.html (Elternkomponente)
<app-timer #stopwatch></app-timer>
<button (click)="startTimer()">Timer starten</button>

// app.component.ts (Elternkomponente)
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { TimerComponent } from './timer.component';

export class AppComponent implements AfterViewInit {
  @ViewChild('stopwatch') timerComponent!: TimerComponent;

  // Zugriff erst nach dem Rendern der View möglich
  ngAfterViewInit() {
    console.log(this.timerComponent);
  }

  startTimer() {
    this.timerComponent.start();
  }
}

@ContentChild funktioniert sehr ähnlich, wird aber verwendet, um auf eine Komponente zuzugreifen, die per Content Projection in die Komponente hineinprojiziert wurde. Der Zugriff ist hier im AfterContentInit-Hook möglich.

// tab.component.ts (projizierte Kindkomponente)
@Component({
  selector: 'app-tab',
  template: `<div *ngIf="active">...</div>`
})
export class TabComponent {
  @Input() title: string = '';
  active = false;
}

// tabs.component.ts (Container-Komponente)
import { Component, ContentChild, AfterContentInit } from '@angular/core';
import { TabComponent } from './tab.component';

@Component({
  selector: 'app-tabs',
  template: `
    <div><ng-content></ng-content></div>
    <p>Aktiver Tab: {{ activeTab?.title }}</p>
  `
})
export class TabsComponent implements AfterContentInit {
  @ContentChild(TabComponent) activeTab?: TabComponent;

  ngAfterContentInit() {
    // Hier haben wir Zugriff auf die erste projizierte TabComponent
    if (this.activeTab) {
      this.activeTab.active = true;
    }
  }
}

// app.component.html (Elternkomponente)
<app-tabs>
  <app-tab title="Profil"></app-tab>
  <app-tab title="Einstellungen"></app-tab>
</app-tabs>

Mit diesen fortgeschrittenen Techniken können Sie komplexe und dennoch lose gekoppelte Komponentenstrukturen erstellen, die Ihre Angular-Anwendungen robust und wartbar machen.

Teste dein Wissen mit ein paar Fragen.

Quiz Questions 1/5

Welcher Decorator wird in einer Kindkomponente verwendet, um ein Ereignis an ihre Elternkomponente zu senden?

Quiz Questions 2/5

Was ist der Zweck des <ng-content>-Tags in einer Angular-Komponente?

Jetzt, da du die fortgeschrittenen Mechanismen der Komponentenkommunikation kennst, bist du bereit, noch anspruchsvollere und interaktivere Anwendungen zu entwickeln.