Zum Inhalt springen

Delete Slice: Löschen ist eine fachliche Entscheidung

Daten zu löschen ist ein Command.

Aber nicht irgendein Command.

Ein Delete ist destruktiv.

Das klingt banal.

Ist es aber nicht.

Denn genau deshalb sollte ein Delete-Flow nicht irgendwo als schneller Button-Handler enden:

click
→ http.delete()
→ reload()
→ toast()

Das ist technisch kurz.

Aber architektonisch schwach.

Die Komponente kennt dann zu viel:

  • sie kennt die destruktive Aktion
  • sie kennt HTTP
  • sie kennt Reload
  • sie kennt Notification
  • sie entscheidet vielleicht noch über Fehlerbehandlung

In diesem Artikel schneide ich Delete deshalb genauso bewusst wie Create:

delete intent
→ facade.deleteArticle(articleId)
→ articleDeleteEvents.deleteRequested({ articleId })
→ ArticleCommandStore
→ ArticleCommand.deleteArticle(command)
→ deleteSucceeded | deleteFailed
→ reload / notification

Der Button löscht nicht.

Der Button sendet eine destruktive Absicht.


Der Button löscht nicht. Der Button sendet eine destruktive Absicht.

Der wichtigste Punkt:

Delete ist keine lokale UI-Aktion.

Delete ist eine Absicht, die das System verändern soll.

Diese Absicht kann erfolgreich sein.

Sie kann scheitern.

Und auf beide Ergebnisse können mehrere unabhängige Reaktionen folgen.

deleteRequested
→ DELETE
→ deleteSucceeded | deleteFailed
→ unabhängige Reaktionen

Das ist ein anderer Schnitt als:

click
→ http.delete()
→ reloadList()
→ toast()

Im direkten Ablauf kennt jeder Schritt zu viel vom nächsten.

Im event-driven Slice sind Absicht, Ausführung und Reaktion getrennt.


Destruktive Aktionen sollten in echten Anwendungen abgefangen werden.

Ein Delete braucht häufig eine Sicherheitsfrage:

delete clicked
→ confirm
→ deleteRequested

Oder eine Undo-Mechanik:

delete clicked
→ soft delete
→ undo möglich
→ endgültig löschen

Das ist wichtig.

Aber es ist nicht der Fokus dieses Artikels.

Hier geht es nicht um Dialog-Design, Undo-Pattern, Soft Delete oder rechtliche Aufbewahrungspflichten.

Hier geht es um den technischen Slice nach der Entscheidung:

Der Benutzer hat bestätigt: Dieser Artikel soll gelöscht werden.

Ab diesem Moment wird aus UI-Interaktion ein Command.

Das Confirm gehört vor den Intent.

Nicht mitten in den Command Store.


Die Versuchung ist groß.

Der Read Store lädt sowieso die Artikel.

Also bekommt er schnell noch eine Methode:

deleteArticle(articleId: string): void {
this.http.delete(`/api/article/${articleId}`).subscribe(() => {
this.reload();
});
}

Das funktioniert.

Aber der Store verändert seine Rolle.

Er ist nicht mehr nur Read Model.

Er ist plötzlich auch Command Handler.

Er lädt Daten.

Er hält ViewModels.

Er löscht Daten.

Er behandelt Fehler.

Er triggert Reload.

Vielleicht zeigt er später noch Toasts.

Und irgendwann heißt er zwar noch ArticleStore, ist aber eigentlich ein kleiner Gott.

Genau das will dieser Slice vermeiden.

ArticleResource
→ kapselt private httpResource
→ stellt article, isLoading, error und reload bereit
Read Store
→ orchestriert Source Signals
→ berechnet ViewModel
→ reagiert auf loadRequested
Command Store
→ hört auf deleteRequested
→ führt DELETE aus
→ feuert deleteSucceeded / deleteFailed
→ leitet Success-Reaktionen als Events weiter

Der Read Store bleibt ein Read Store.

Der Command Store bleibt ein Command Store.


Eine mögliche Struktur für diesen Slice:

article/
├── infrastructure/
│ ├── article.command.ts
│ ├── article.dto.ts
│ ├── article.mapper.ts
│ └── article.resource.ts
├── +state/
│ ├── article.store.ts
│ ├── article.vm.ts
│ ├── article-view-model.mapper.ts
│ ├── delete-article.command.ts
│ ├── article-delete.events.ts
│ ├── article-read.events.ts
│ └── article-command.store.ts
├── application/
│ └── article-delete.facade.ts
└── presentation/
└── article-card.component.html

Wenn Create und Delete im selben Feature leben, kann der ArticleCommandStore natürlich beide Write-Flows behandeln.

Dann sieht die Command-Seite eher so aus:

+state/
├── create-article.command.ts
├── delete-article.command.ts
├── article-create.events.ts
├── article-delete.events.ts
└── article-command.store.ts
infrastructure/
└── article.command.ts

Das Pattern bleibt gleich:

ArticleCommandStore
→ nutzt ArticleCommand

Der Store entscheidet, wann eine Write-Operation ausgeführt wird.

Die Infrastructure entscheidet, wie dieser Write gegen die externe API übersetzt wird.


Ein Delete braucht nicht viel Payload.

Aber die Absicht sollte trotzdem explizit sein.

export interface DeleteArticleCommand {
readonly articleId: string;
}

Das ist kein DTO.

Das ist kein ViewModel.

Das ist kein Button-Event.

Das ist die fachliche Absicht:

DeleteArticleCommand
= Benutzer möchte diesen Artikel löschen

Bei Delete ist diese Explizitheit wichtig.

Denn wir modellieren keine technische HTTP-Methode.

Wir modellieren eine destruktive Handlung.


Jetzt definieren wir die Events des Delete-Slice.

import { eventGroup, type } from '@ngrx/signals/events';
import { DeleteArticleCommand } from './delete-article.command';
export const articleDeleteEvents = eventGroup({
source: 'Article Delete',
events: {
deleteRequested: type<DeleteArticleCommand>(),
deleteSucceeded: type<{ readonly articleId: string }>(),
deleteFailed: type<{
readonly articleId: string;
readonly error: unknown;
}>(),
},
});

Die Sprache ist wieder wichtiger als die Syntax:

deleteRequested
deleteSucceeded
deleteFailed

deleteRequested ist noch kein Ergebnis.

Es ist eine Absicht.

deleteSucceeded und deleteFailed sind Ergebnisse der Ausführung.

Ich nehme die articleId auch in deleteFailed mit auf.

Warum?

Weil ein Fehler ohne Kontext oft wenig wert ist.

Gerade in Listen möchte man wissen:

Welcher Delete ist fehlgeschlagen?

Das macht Fehlerreaktionen testbarer und nachvollziehbarer.


Nach einem erfolgreichen Delete sollen die gelesenen Daten aktualisiert werden.

Aber der Command Store soll den Read Store nicht direkt kennen.

Nicht so:

deleteSucceeded
→ articleStore.reload()

Sondern so:

deleteSucceeded
→ articleReadEvents.loadRequested()
→ Read Store stößt ArticleResource.reload() an

Dafür gibt es ein Read Event:

import { eventGroup, type } from '@ngrx/signals/events';
export const articleReadEvents = eventGroup({
source: 'Article Read',
events: {
loadRequested: type<void>(),
},
});

Das wirkt wie eine kleine Indirektion.

Aber genau diese Indirektion ist der Schnitt.

Die Write-Seite sagt nicht, wie die Read-Seite lädt.

Sie sagt nur:

Nach diesem erfolgreichen Write sollten die gelesenen Artikeldaten neu angefordert werden.


Die Infrastructure bündelt die Write-Operationen.

In diesem Slice liegt dafür eine Datei article.command.ts.

import { HttpClient } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { DeleteArticleCommand } from '../+state/delete-article.command';
@Injectable()
export class ArticleCommand {
private readonly http = inject(HttpClient);
readonly deleteArticle = ({ articleId }: DeleteArticleCommand) => this.http.delete<void>(`https://lorem-api.com/api/article/${articleId}`);
}

Der Name ist bewusst ArticleCommand.

Nicht ArticleResource.

Nicht ArticleStore.

Diese Klasse bündelt Write-Operationen gegen die externe API.

DeleteArticleCommand
→ fachliche Absicht
ArticleCommand
→ Infrastructure-Operationen für Write-Zugriffe

Bei Create entsteht hier häufig ein DTO.

Bei Delete reicht oft die ID in der URL.

Wenn die externe API später ein anderes Format braucht, bleibt diese Entscheidung an der API-Grenze.

Nicht in der Komponente.

Nicht in der Facade.

Nicht im Read Store.


5. Command Store: requested ausführen, Ergebnis feuern

Abschnitt betitelt „5. Command Store: requested ausführen, Ergebnis feuern“

Jetzt kommt der Command Store.

Er hält kein ViewModel.

Er rendert nichts.

Er hört auf Events, führt die Infrastructure aus und veröffentlicht Ergebnis-Events.

import { inject } from '@angular/core';
import { signalStore, withProps } from '@ngrx/signals';
import { Events, withEventHandlers } from '@ngrx/signals/events';
import { mapResponse } from '@ngrx/operators';
import { exhaustMap, map } from 'rxjs';
import { ArticleCommand } from '../infrastructure/article.command';
import { articleDeleteEvents } from './article-delete.events';
import { articleReadEvents } from './article-read.events';
export const ArticleCommandStore = signalStore(
withProps(() => ({
_articleCommand: inject(ArticleCommand),
})),
withEventHandlers(({ _articleCommand }, events = inject(Events)) => ({
deleteArticle$: events.on(articleDeleteEvents.deleteRequested).pipe(
exhaustMap((command) =>
_articleCommand.deleteArticle(command).pipe(
mapResponse({
next: () =>
articleDeleteEvents.deleteSucceeded({
articleId: command.articleId,
}),
error: (error: unknown) =>
articleDeleteEvents.deleteFailed({
articleId: command.articleId,
error,
}),
}),
),
),
),
reloadOnDeleteSucceeded$: events.on(articleDeleteEvents.deleteSucceeded).pipe(map(() => articleReadEvents.loadRequested())),
// notifyOnDeleteSucceeded$: events
// .on(articleDeleteEvents.deleteSucceeded)
// .pipe(
// map(() =>
// notificationEvents.showSuccess({
// summary: {
// key: 'articles.notifications.delete.success.summary',
// },
// detail: {
// key: 'articles.notifications.delete.success.detail',
// },
// }),
// ),
// ),
// notifyOnDeleteFailed$: events
// .on(articleDeleteEvents.deleteFailed)
// .pipe(
// map(() =>
// notificationEvents.showError({
// summary: {
// key: 'articles.notifications.delete.error.summary',
// },
// detail: {
// key: 'articles.notifications.delete.error.detail',
// },
// }),
// ),
// ),
})),
);

Der Store macht drei Dinge:

1. deleteRequested entgegennehmen
2. DELETE über Infrastructure ausführen
3. deleteSucceeded oder deleteFailed veröffentlichen

Danach übersetzt er deleteSucceeded in ein Read Event:

deleteSucceeded
→ loadRequested

Das ist absichtlich kein direkter Aufruf auf den Read Store.

Der Command Store kennt nicht die Resource.

Er kennt nicht das ViewModel.

Er kennt nicht die Presentation.

Er kennt nur Events.


Der DELETE-Aufruf hat zwei mögliche Ergebnisse:

success
→ deleteSucceeded
error
→ deleteFailed

Genau dafür ist mapResponse gut lesbar.

Der Erfolgsfall erzeugt ein Success Event.

Der Fehlerfall erzeugt ein Error Event.

mapResponse({
next: () =>
articleDeleteEvents.deleteSucceeded({
articleId: command.articleId,
}),
error: (error: unknown) =>
articleDeleteEvents.deleteFailed({
articleId: command.articleId,
error,
}),
});

Das hält den Command Store deklarativ.

Kein verschachteltes subscribe.

Kein lokales Error-Handling in der Komponente.

Kein direkter Toast im Catch.

Nur:

HTTP-Ergebnis
→ Event

Auch Delete kann doppelt ausgelöst werden.

Der Benutzer klickt zweimal.

Die Liste rendert langsam.

Ein Button bleibt zu lange aktiv.

Für diesen Artikel nutze ich deshalb exhaustMap.

exhaustMap
→ ignoriert weitere Deletes, solange einer läuft
concatMap
→ queued Deletes nacheinander
mergeMap
→ erlaubt parallele Deletes
switchMap
→ bricht alte Deletes ab

Bei destruktiven Commands ist switchMap meist keine gute Grundeinstellung.

Ein Delete ist keine Suchanfrage.

Wenn ein Delete einmal unterwegs ist, will ich ihn normalerweise nicht still abbrechen, nur weil ein zweites Event kommt.

exhaustMap ist für diesen einfachen Slice die konservative Wahl.


Nach einem erfolgreichen Delete wollen wir in diesem Artikel neu laden.

Später vielleicht auch eine Notification zeigen.

Wichtig ist die Richtung:

deleteSucceeded
→ articleReadEvents.loadRequested()
deleteSucceeded
→ notificationEvents.showSuccess(...)

Das sind Event-Reaktionen.

Keine direkten imperativen Side Effects im Command Store.

Deshalb nutze ich hier map.

Aus einem Event wird ein neues Event.

reloadOnDeleteSucceeded$: events
.on(articleDeleteEvents.deleteSucceeded)
.pipe(
map(() => articleReadEvents.loadRequested()),
),

Das ist testbarer als:

tap(() => articleStore.reload());

Und es hält die Kopplung klein.

Der Command Store sagt nicht:

Read Store, lade dich neu.

Er sagt nur:

Ein Load wurde angefordert.

Die Read-Seite entscheidet selbst, was das bedeutet.

Auch Notifications können so modelliert werden:

notifyOnDeleteSucceeded$: events
.on(articleDeleteEvents.deleteSucceeded)
.pipe(
map(() =>
notificationEvents.showSuccess({
summary: {
key: 'articles.notifications.delete.success.summary',
},
detail: {
key: 'articles.notifications.delete.success.detail',
},
}),
),
),

Dieser Artikel implementiert die Notification nicht vollständig.

Er deutet sie nur an.

Der Punkt ist:

deleteSucceeded
→ loadRequested
→ showSuccess

Solange eine Reaktion wieder als Event beschrieben werden kann, bleibt map die sauberere Wahl.

tap gehört an echte imperative Grenzen: Router, Toast-Service, Logging oder externe APIs.


Die konkrete Angular Resource bleibt in der Infrastructure.

ArticleResource hält die httpResource privat und stellt nach außen nur eine kleine, benannte API bereit:

article: Signal<Article | null>
isLoading: Signal<boolean>
error: Signal<Error | undefined>
reload(): void

Der Read Store kennt deshalb keine HttpResourceRef.

Er kennt weder hasValue() noch das Wurfverhalten von value().

Er orchestriert nur die von ArticleResource bereitgestellten Source Signals, projiziert daraus das ViewModel und reagiert auf articleReadEvents.loadRequested.

import { computed, inject } from '@angular/core';
import { signalStore, withComputed, withProps } from '@ngrx/signals';
import { Events, withEventHandlers } from '@ngrx/signals/events';
import { tap } from 'rxjs';
import { ArticleResource } from '../infrastructure/article.resource';
import { articleReadEvents } from './article-read.events';
import { toArticleViewModel } from './article-view-model.mapper';
export const ArticleStore = signalStore(
withProps(() => ({
_articleResource: inject(ArticleResource),
})),
withComputed(({ _articleResource }) => ({
isLoading: _articleResource.isLoading,
error: _articleResource.error,
vm: computed(() => toArticleViewModel(_articleResource.article())),
})),
withEventHandlers(({ _articleResource }, events = inject(Events)) => ({
reloadOnLoadRequested$: events.on(articleReadEvents.loadRequested).pipe(tap(() => _articleResource.reload())),
})),
);

Der ViewModel-Mapper erhält dabei:

Article | null
→ ArticleVm | null

Die Domain Entity selbst bleibt strikt.

Es gibt keinen künstlichen leeren Artikel, nur um den technischen Lifecycle zu verstecken.

Hier ist tap in Ordnung.

Nicht, weil der Store selbst HTTP- oder Resource-Logik ausführt.

Sondern weil der Event-Flow an dieser Stelle eine explizite Infrastructure-Operation anstößt:

loadRequested
→ ArticleResource.reload()

Davor werden Events auf Events abgebildet.

Hier wird die Read-Operation tatsächlich ausgelöst.

Der Store interpretiert dabei keinen technischen Resource-Lifecycle. Er orchestriert lediglich einen Read-Intent zu einer klar benannten Operation seiner Infrastructure-Abhängigkeit.

Das ist die gewünschte Rollenverteilung:

private httpResource
→ ArticleResource
→ Source Signals und reload()
→ Read Store
→ ViewModel

Der Store entscheidet weiterhin, welche Signale der Slice nach außen anbietet und wie daraus das ViewModel entsteht.

Er entscheidet aber nicht mehr fallweise, ob eine Angular Resource gerade sicher gelesen werden darf.

Das hält den Lesefluss ruhig:

article → ViewModel
loadRequested → reload()

Beim Lesen des Stores interessiert die Orchestrierung.

Nicht die Gebrauchsanweisung der darunterliegenden Framework-API.


Die Delete-Facade führt keinen HTTP-Aufruf aus.

Sie kennt keinen HttpClient.

Sie kennt auch die Resource nicht.

Sie veröffentlicht nur die Absicht.

import { Injectable } from '@angular/core';
import { injectDispatch } from '@ngrx/signals/events';
import { articleDeleteEvents } from '../+state/article-delete.events';
@Injectable()
export class ArticleDeleteFacade {
private readonly dispatchDelete = injectDispatch(articleDeleteEvents);
readonly deleteArticle = (articleId: string): void => {
this.dispatchDelete.deleteRequested({ articleId });
};
}

Das ist die öffentliche API der Delete-UI:

deleteArticle(articleId)

Die Facade löscht keinen Artikel.

Sie veröffentlicht die Absicht, einen Artikel zu löschen.

Presentation
→ facade.deleteArticle(articleId)
→ articleDeleteEvents.deleteRequested({ articleId })

Was danach passiert, gehört nicht mehr der Presentation.


9. Presentation: Button sendet destruktive Absicht

Abschnitt betitelt „9. Presentation: Button sendet destruktive Absicht“

Das Template bleibt klein.

<button type="button" class="article-card__delete" (click)="facade.deleteArticle(article.id)">Artikel löschen</button>

Das Beispiel zeigt bewusst keinen Confirm-Dialog.

Nicht, weil Confirm unwichtig wäre.

Sondern weil Confirm eine UI-Sicherheitsentscheidung vor dem Command ist.

Der hier gezeigte Slice beginnt erst nach dieser Entscheidung:

Benutzer will löschen
→ deleteRequested({ articleId })

In einer echten Anwendung würde ich destruktive Aktionen fast immer absichern: Confirm, Undo, Soft Delete oder rollenabhängige Berechtigungen.

Aber diese Sicherheitsmechanik sollte den Command-Flow nicht verdecken.

Der Button ruft hier nur die Facade auf.

Die Facade sendet den Intent.

Alles Weitere liegt im Event-Flow.


Für den Artikel kann der Slice lokal an der Page oder Route bereitgestellt werden.

providers: [ArticleResource, ArticleCommand, ArticleStore, ArticleCommandStore, ArticleDeleteFacade];

In einer echten Anwendung würde ich diese Provider häufig eher an die Route hängen.

Für den Artikel ist die lokale Variante aber hilfreich, weil der Slice dadurch sichtbar geschlossen bleibt.

Wichtig ist:

ArticleResource
ArticleCommand
ArticleStore
ArticleCommandStore
ArticleDeleteFacade

bilden gemeinsam den Scope dieses Delete-Slice.

ArticleResource kapselt dabei die private httpResource.

ArticleStore konsumiert ihre Source Signals und orchestriert den Read-Flow.

Der ArticleCommandStore ist hier bewusst Teil dieses Scopes.

Allerdings ist das nur die Scope-Entscheidung.

In einer echten Anwendung muss der Command Store auch tatsächlich einmal im Lifecycle dieses Slice instanziiert werden, damit seine Event Handler aktiv sind.

Das ist eine kleine mechanische Reibung dieses Ansatzes.

Je nach Projekt kann das zum Beispiel über die Route, einen bewusst injizierten Slice-Service oder eine andere Lifecycle-Stelle passieren.

Ich zeige hier bewusst keinen versteckten Constructor-Hack wie inject(ArticleCommandStore) nur zur Instanziierung.

Das würde den Code zwar möglicherweise zum Laufen bringen, aber die Abhängigkeit verschleiern.

Für den Artikel ist wichtiger:

Command Store existiert im Slice-Scope
→ hört auf deleteRequested
→ feuert deleteSucceeded / deleteFailed

Wie genau dieser Lifecycle im Projekt verdrahtet wird, ist eine Framework-Mechanik.

Der fachliche Schnitt bleibt davon unberührt.


11. Delete, Create und gemeinsame Write-Reaktionen

Abschnitt betitelt „11. Delete, Create und gemeinsame Write-Reaktionen“

In einem echten Feature steht Delete selten allein.

Create, Update und Delete teilen häufig dieselben Reaktionen.

Zum Beispiel:

createSucceeded
updateSucceeded
deleteSucceeded
→ loadRequested

Dann kann der Command Store diese Success Events gemeinsam behandeln:

reloadOnWriteSucceeded$: events
.on(
articleCreateEvents.createSucceeded,
articleUpdateEvents.updateSucceeded,
articleDeleteEvents.deleteSucceeded,
)
.pipe(
map(() => articleReadEvents.loadRequested()),
),

Das ist stärker als drei einzelne Reload-Aufrufe in drei Komponenten.

Der Reload hängt nicht am Button.

Er hängt auch nicht am konkreten Command.

Er hängt am erfolgreichen Write.

Write succeeded
→ Read neu anfordern

Das ist die eigentliche Abstraktion.


Auch Fehler sollten nicht einfach als lokales catchError in der Komponente enden.

Ein Fehler ist ebenfalls ein Ergebnis des Commands.

deleteRequested
→ DELETE
→ deleteFailed({ articleId, error })

Was danach passiert, ist wieder eine Reaktion:

deleteFailed
→ notificationEvents.showError(...)
deleteFailed
→ // keep row visible
deleteFailed
→ // rollback optimistic state

Für diesen Artikel reicht das Event.

Eine echte UI könnte später auf deleteFailed reagieren und einen Toast anzeigen, einen Dialog offen lassen oder einen optimistischen UI-Zustand zurücknehmen.

Wichtig ist:

Der Command Store entscheidet nicht, wie der Fehler angezeigt wird.

Er veröffentlicht nur:

Der Delete ist fehlgeschlagen.


Für einen einzelnen Button kann dieser Schnitt nach mehr Code aussehen.

Das stimmt.

Ein direkter http.delete()-Aufruf ist kürzer.

Aber kürzer ist nicht automatisch einfacher.

Ein direkter Aufruf ist oft nur deshalb kurz, weil er Kopplung versteckt.

Button kennt Delete
Button kennt HTTP
Button kennt Reload
Button kennt Toast
Button kennt Fehlerbehandlung

Der event-driven Slice macht diese Übergänge sichtbar.

Intent
→ Command
→ Result
→ Reaction

Das ist nicht für jeden kleinen Button nötig.

Aber es ist wertvoll, sobald ein Delete mehr als eine lokale Folge hat.

Also zum Beispiel:

  • Liste neu laden
  • Detailseite verlassen
  • Toast anzeigen
  • Dialog schließen
  • Cache invalidieren
  • mehrere Read Models aktualisieren
  • optimistischen Zustand zurückrollen

Dann ist ein Event kein akademischer Umweg.

Es ist eine Entkopplung.


Ein Delete ist kein schneller HTTP-Aufruf aus der Komponente.

Ein Delete ist ein destruktiver Command.

Die UI sollte diese Absicht nicht selbst orchestrieren.

Sie sendet nur:

deleteArticle(articleId)
→ deleteRequested({ articleId })

Der Command Store führt den Write aus.

Die Infrastructure spricht mit der API.

Success und Error werden als Events sichtbar.

Reload und Notification hängen nicht am Button.

Sie hängen am Ergebnis.

deleteRequested
→ ArticleCommand.deleteArticle(command)
→ deleteSucceeded | deleteFailed
deleteSucceeded
→ articleReadEvents.loadRequested()
→ notificationEvents.showSuccess(...)
deleteFailed
→ notificationEvents.showError(...)

So bleibt die destruktive Aktion explizit.

Der Read Store bleibt ein Read Store.

Die Infrastructure kapselt ihre technische Resource selbst.

Der Read Store orchestriert nur Source Signals, Projektionen und Read-Intents.

Der Command Store bleibt ein Command Store.

Und die Komponente wird nicht zum Ort, an dem Löschen, Nachladen, Navigation und Toasts zusammenkleben.