AngularAtlasAusg. 22.2

Kap. 05Komponenten-Design28 / 75

Generische Komponenten <T>

Eine korrekt typisierte DataTable<T> oder Select<T>, mit Templates für Zellen.

Architektur ~0 Min. Lesezeit Offizielle Doku

Eine generische Komponente hat einen Typparameter — DataTable<T>, Select<T>, List<T> — und funktioniert mit beliebigen Daten, wobei die Typsicherheit im Template und in den Outputs erhalten bleibt. Angular leitet T aus dem Input ab, den du bindest (zum Beispiel aus [rows]), genau wie bei einer generischen Funktion in TypeScript.

Beispiel: DataTable<T>

Die Tabelle kümmert sich um das Verhalten (Sortierung, Auswahl, leere Zeile), und wer sie verwendet, liefert die Spalten (typisierte Konfiguration) und optional Templates für besondere Zellen.

export interface Column<T> {
  key: Extract<keyof T, string>;      // nur echte Schlüssel von T
  header: string;
  sortable?: boolean;
  align?: 'left' | 'right';
  format?: (row: T) => string;
}

@Component({
  selector: 'app-data-table',
  imports: [NgTemplateOutlet],
  template: `
    <table>
      <thead>
        <tr>
          @for (col of columns(); track col.key) { <th (click)="toggleSort(col.key)">{{ col.header }}</th> }
        </tr>
      </thead>
      <tbody>
        @for (row of sortedRows(); track trackBy()(row)) {
          <tr (click)="rowClick.emit(row)">
            @for (col of columns(); track col.key) {
              <td>
                @if (cellTemplates().get(col.key); as tpl) {
                  <ng-container [ngTemplateOutlet]="tpl" [ngTemplateOutletContext]="{ $implicit: row, value: row[col.key] }" />
                } @else {
                  {{ col.format ? col.format(row) : row[col.key] }}
                }
              </td>
            }
          </tr>
        }
      </tbody>
    </table>
  `,
})
export class DataTable<T> {                       // ← der Typparameter der Komponente
  readonly rows = input.required<readonly T[]>(); // ← T wird von hier abgeleitet
  readonly columns = input.required<readonly Column<T>[]>();
  readonly trackBy = input<(row: T) => unknown>((row) => row);
  readonly rowClick = output<T>();               // ← typisierter Output

  private readonly cellDirectives = contentChildren(CellTemplate<T>);
  protected readonly cellTemplates = computed(
    () => new Map(this.cellDirectives().map((d) => [d.appCell(), d.template] as const)),
  );

  protected readonly sort = signal<{ key: keyof T; dir: 'asc' | 'desc' } | null>(null);
  protected readonly sortedRows = computed(() => { /* Sortierung nach this.sort() */ });
}
LiveDieselbe generische Tabelle für Benutzer und Produkte

DataTable<UserDto>

Ana Popescu Cluj-Napoca admin 2021
Mihai Ionescu București editor 2022
Ioana Dumitru Iași viewer 2023
Radu Stan Timișoara editor 2020
Elena Marin Brașov viewer 2024
Andrei Georgescu Cluj-Napoca admin 2019
Maria Constantin Constanța viewer 2023
Vlad Petrescu Sibiu editor 2022
Cristina Pop Oradea viewer 2024
Bogdan Rusu Iași viewer 2021

Letzter Klick, typisiert als UserDto: — klicke auf eine Zeile

DataTable<ProductDto>

Kategorie
Laptop Pro 14 laptop ★ 4.7 4 Stk. 7.499 RON
Laptop Air 13 laptop ★ 4.4 ausverkauft4.999 RON
Smartphone X phone ★ 4.6 12 Stk. 3.999 RON
Smartphone Mini phone ★ 4.1 7 Stk. 2.499 RON
Studio-Kopfhörer audio ★ 4.8 20 Stk. 1.299 RON
Tragbarer Lautsprecher audio ★ 4 ausverkauft449 RON
Mouse Ergo accessory ★ 4.5 35 Stk. 349 RON
Mechanische Tastatur accessory ★ 4.9 9 Stk. 699 RON
Hub USB-C accessory ★ 3.9 50 Stk. 229 RON
Monitor 27" accessory ★ 4.6 3 Stk. 1.899 RON
Sortiere die Spalten. Klicke auf einen Benutzer: Der rowClick-Output ist als UserDto typisiert, daher existiert .email ohne Cast.

So verwendest du sie

<app-data-table
  [rows]="users()"
  [columns]="userColumns"
  [trackBy]="byId"
  (rowClick)="open($event)"      <!-- $event: UserDto -->
>
  <ng-template appCell="role" [appCellOf]="users()" let-user>
    <app-role-badge [role]="user.role" />   <!-- user: UserDto, Autocomplete funktioniert -->
  </ng-template>
</app-data-table>

Noch ein Beispiel: Select<T>

// Ein weiteres klassisches Beispiel: ein Select<T>, das mit jedem Objekt funktioniert
@Component({
  selector: 'app-select',
  template: `
    <select (change)="pick($any($event.target).selectedIndex - 1)">
      <option disabled [selected]="!value()">{{ placeholder() }}</option>
      @for (o of options(); track $index) {
        <option [selected]="isSelected(o)">{{ displayWith()(o) }}</option>
      }
    </select>
  `,
})
export class Select<T> {
  readonly options = input.required<readonly T[]>();
  readonly value = model<T | null>(null);
  readonly displayWith = input<(item: T) => string>((item) => String(item));
  readonly compareWith = input<(a: T, b: T) => boolean>((a, b) => a === b);
  readonly placeholder = input('Auswählen…');

  protected isSelected(o: T) {
    const v = this.value();
    return v !== null && this.compareWith()(o, v);
  }
  protected pick(i: number) {
    this.value.set(this.options()[i] ?? null);
  }
}

// <app-select [options]="countries()" [(value)]="country" [displayWith]="byName" />

Komposition: ein Domänen-Wrapper um die generische Komponente

Die generische Komponente darf nichts über die Domäne wissen. Wenn du dieselbe Benutzertabelle auf drei Seiten hast, fügst du kein showRoleColumn zu DataTable hinzu — du baust eine UsersTable im Users-Feature, die sie umschließt.

// Komposition: Die Domänenkomponente umschließt die generische Komponente.
// DataTable<T> liegt in shared/ui; UsersTable liegt in features/users/ui.
@Component({
  selector: 'app-users-table',
  imports: [DataTable, CellTemplate, RoleBadge],
  template: `
    <app-data-table [rows]="users()" [columns]="columns" [trackBy]="byId" clickable (rowClick)="open.emit($event)">
      <ng-template appCell="role" [appCellOf]="users()" let-user>
        <app-role-badge [role]="user.role" />
      </ng-template>
    </app-data-table>
  `,
})
export class UsersTable {
  readonly users = input.required<User[]>();
  readonly open = output<User>();
  protected readonly byId = (u: User) => u.id;
  protected readonly columns: Column<User>[] = [/* die Domänenspalten an einer Stelle */];
}

Anti-Pattern: die „God Component“

// ✗ Die „generische Komponente“, die außer Kontrolle geraten ist
@Component({ selector: 'app-table' })
export class Table {
  @Input() data: any[] = [];                  // any → null Sicherheit
  @Input() showRoleColumn = false;            // Domänenwissen in shared
  @Input() roleColors: Record<string, string> = {};
  @Input() isProductTable = false;            // ifs auf „was für eine Tabelle bin ich“
  @Input() priceCurrency = 'RON';
  @Input() highlightAdmins = true;
  @Input() exportToExcel = false;
  // ... 30 weitere Inputs
}
// Die Lösung: generische Typen + Zell-Templates + Domänen-Wrapper.

Wann mache ich sie generisch?

Generisch machen
  • Dasselbe Verhalten auf unterschiedlichen Daten: Tabelle, Liste, Select, Autocomplete, Tree, Paginator.
  • Du hast bereits 2–3 echte Verwendungen (nicht „vielleicht brauchen wir es“).
  • Du kannst die API ohne Domänenwörter beschreiben (kein „User“, „Order“, „Invoice“).
  • Du willst sie in shared/ui oder in eine Bibliothek legen.
Spezifisch lassen
  • Du hast nur eine einzige Verwendung — voreilige Verallgemeinerung kostet mehr als Duplizierung.
  • Die Unterschiede liegen im Verhalten, nicht in den Daten (dann hast du zwei verschiedene Komponenten).
  • Die API bräuchte Flags wie isProductTable.
  • Es ist eine Domänenkomponente (InvoiceSummary) — sie bleibt im Feature.

Konfiguration vs. Templates

  • Konfiguration (columns mit format): für die einfachen Fälle, 80 % der Spalten.
  • Templates (appCell): für aufwendige Zellen (Badges, Buttons, Links).
  • Projektion (<ng-content>): für feste Bereiche (Toolbar, Footer).

Für UI-Teile, die der Verwender der Komponente bestimmt, siehe ng-template & ngTemplateOutlet; für die Typen hinter T Interfaces & Modelle.

Häufige Stolperfallen

  • T wird als unknown abgeleitet: wenn der Input, aus dem abgeleitet wird, fehlt oder any ist. Typisiere die Daten an der Quelle.
  • Die let-*-Template-Variablen sind any ohne ngTemplateContextGuard — füge die Direktive mit dem Guard hinzu.
  • Das implizite track row ist für stabile Daten in Ordnung; bei Daten, die vom Server neu geladen werden, gib ein trackBy auf die ID an.
  • Zu viele Inputs: Mehr als etwa 8–10 sind ein Zeichen dafür, dass du Verantwortlichkeiten vermischst. Teile die Komponente auf oder nutze Templates.

Kap. 05 · 0 von 6 abgeschlossen

Lektion abgeschlossen?

Markiere sie, dann erscheint sie auf deiner Karte: im Inhaltsverzeichnis und auf der Startseite.

Wird automatisch markiert, sobald du die ganze Lektion gelesen hast.

  1. Smart vs. Dumb
  2. Generische Komponenten <T>
  3. Dynamische Komponenten
  4. Direktiven & Host-Komposition
  5. Eigene Formular-Controls
  6. Change Detection
Deine ganze Karte ansehen

Alle 75 Seiten · tippen zum Filtern

  • 01Was ist neu in Angular 21 & 22Alle wichtigen Änderungen in v21 und v22 in Kürze, mit Link zur Seite, die sie jeweils erklärt.Start
  • 01Deine erste App, Schritt für SchrittEine Leseliste, von einer leeren Komponente bis zur fertigen App — Signals, @for, Inputs und Outputs, ein Service und ein Formular, Schritt für Schritt.Start
  • 01Schnelle Entscheidungen„Wohin damit?“, „Signal oder Observable?“, „Shared oder Feature?“ — kurze Antworten auf Alltagsfragen.Start
  • 01Angular-Interviewfragen60 echte Interviewfragen für Junior, Mid und Senior, mit der passenden Antwort, der Nachfrage und den Warnsignalen. Dazu ein Probeinterview.Start
  • 02Standalone & BootstrapWie eine App ohne NgModule startet: bootstrapApplication, app.config.ts, imports in der Komponente.Moderne Grundlagen
  • 02Data Binding & EventsInterpolation, Property- und Attribut-Bindings, Events mit $event und Tastenfiltern und wie [( )] aufgebaut ist — was in das DOM geht und was zurückkommt.Moderne Grundlagen
  • 02Control Flow: @if, @for, @switchDie Block-Syntax, die *ngIf/*ngFor/ngSwitch ersetzt, mit track, @empty und Exhaustive Checks.Moderne Grundlagen
  • 02@let und die neuen Template-AusdrückeLokale Variablen mit @let, Arrow Functions, Spread, Regex, instanceof, Kommentare in Tags.Moderne Grundlagen
  • 02@defer — Lazy Loading in TemplatesSchwere Komponenten nur laden, wenn sie gebraucht werden: Trigger, Prefetch, Placeholder, Loading, Error.Moderne Grundlagen
  • 02Class- & Style-BindingsWarum [class.x] und [style.x] ngClass und ngStyle abgelöst haben (mit automatischer Migration ab v21).Moderne Grundlagen
  • 02Komponenten-Styles & Kapselung:host, die drei Kapselungsmodi, Theming mit CSS-Variablen und warum ::ng-deep der letzte Ausweg ist.Moderne Grundlagen
  • 02Pipes: wann und wiePure vs. Impure Pipes, eigene Pipes und wie du zwischen Pipe, computed() und Methode wählst.Moderne Grundlagen
  • 03signal() und computed()Die reaktiven Grundbausteine: beschreibbarer Zustand, abgeleiteter Zustand, Gleichheit, untracked und Regeln zur Immutability.Signals & Reaktivität
  • 03effect() — wann ja, wann neinMit Signals synchronisierte Seiteneffekte: localStorage, Logging, imperative APIs. Und ihre Stolperfallen.Signals & Reaktivität
  • 03linkedSignal() — editierbarer abgeleiteter ZustandEin Signal, das sich zurücksetzt, wenn sich seine Quelle ändert, das du aber auch manuell setzen kannst.Signals & Reaktivität
  • 03resource, rxResource, httpResourceAsynchrone Daten als Signals: status, value, error, reload, automatischer Abbruch. Stabil seit v22.Signals & Reaktivität
  • 03debounced() — verzögerte SignalsDie „Debounce“-Variante eines Signals, ohne RxJS. Perfekt für die Suche.Signals & Reaktivität
  • 03Listen: suchen, filtern, sortieren, blätternEine Kette von computed-Signals, die nur neu berechnet, was sich geändert hat — mit den Filtern in der URL, damit ein Link dieselbe Ansicht öffnet.Signals & Reaktivität
  • 03Observables von null anWas ein Observable ist, subscribe und unsubscribe, die Operatoren, die du jede Woche brauchst, cold vs. hot — und wann ein Signal reicht.Signals & Reaktivität
  • 03Signals ↔ RxJStoSignal, toObservable, takeUntilDestroyed, outputFromObservable — und wann RxJS weiterhin die richtige Wahl bleibt.Signals & Reaktivität
  • 04input() und output()Inputs als Signals (required, transform, alias) und typisierte Outputs. Daten nach unten, Events nach oben.Komponenten: Kommunikation
  • 04model() — Two-Way-BindingBanana-in-a-Box [( )] mit einer einzigen API. Wann du es nutzt und wann input + output besser ist.Komponenten: Kommunikation
  • 04viewChild & contentChildQueries als Signals: viewChild, viewChildren, contentChild, contentChildren, die Option read.Komponenten: Kommunikation
  • 04Lifecycle & DestroyRefWelche Hooks in der Signals-Ära noch zählen, afterNextRender, DestroyRef und typisiertes ngOnChanges.Komponenten: Kommunikation
  • 04Content Projectionng-content mit select, mehrere Slots, Standardinhalt und die Falle der bedingten Projektion.Komponenten: Kommunikation
  • 04ng-template & ngTemplateOutletTemplates als Werte: Render Props in Angular, typisierter Kontext und Anpassen von Komponenten.Komponenten: Kommunikation
  • 05Smart vs. DumbContainer vs. Presentational: wer den Zustand hält, wer nur anzeigt und wie du die beiden sauber trennst.Komponenten-Design
  • 05Generische Komponenten <T>Eine korrekt typisierte DataTable<T> oder Select<T>, mit Templates für Zellen.Komponenten-Design
  • 05Dynamische KomponentenNgComponentOutlet, createComponent mit inputBinding/outputBinding und ein Dashboard, das aus einer Konfiguration gebaut wird.Komponenten-Design
  • 05Direktiven & Host-KompositionAttribut-Direktiven, die host: {}-Metadaten und die Directive Composition API (hostDirectives).Komponenten-Design
  • 05Eigene Formular-ControlsFormValueControl (Signal Forms) vs. ControlValueAccessor: eine Sternebewertung, in beiden Varianten.Komponenten-Design
  • 05Change DetectionOnPush ist ab v22 der Standard, zone.js entfällt ab v21. Was das Rendering auslöst und wie du dir nicht die Finger verbrennst.Komponenten-Design
  • 06inject() & @Service()Der neue @Service()-Decorator ab v22, inject() vs. Konstruktor und der Injection-Kontext.Dependency Injection
  • 06Provider & die Injector-HierarchieRoot vs. Route vs. Komponente, useClass/useValue/useFactory/useExisting und die Modifier optional/self/skipSelf.Dependency Injection
  • 06InjectionToken & provideX()Typisierte Konfiguration, provideFeature()-Funktionen, makeEnvironmentProviders und Initializer.Dependency Injection
  • 06injectAsync() — Lazy ServicesLade einen schweren Service erst, wenn du ihn brauchst, mit Prefetch im Leerlauf.Dependency Injection
  • 07Ordnerstrukturcore / shared / features, was in welchen Ordner gehört und wie ein Feature von innen aussieht.Architektur der Anwendung
  • 07Shared vs. FeatureWann du eine Komponente nach Shared verschiebst und (vor allem) wann NICHT. Eine konkrete Checkliste.Architektur der Anwendung
  • 07Interfaces & ModelleInline-Interface oder eigene Datei? DTO vs. Domänenmodell, type vs. interface, wohin damit.Architektur der Anwendung
  • 07State Management mit SignalsEinfache Signal-Stores, Fassaden, lokaler vs. globaler Zustand und wann du NgRx brauchst.Architektur der Anwendung
  • 07Styleguide & NamensgebungDie aktuellen offiziellen Konventionen: Dateinamen ohne Suffixe, protected, readonly, Reihenfolge der Member.Architektur der Anwendung
  • 07Barrels, Aliase & Grenzenindex.ts, Pfad-Aliase, die öffentliche API eines Features und Abhängigkeitsregeln, die sich automatisch prüfen lassen.Architektur der Anwendung
  • 07Fehler: @boundary & ErrorHandlerDer neue @boundary-Block ab v22.2, der globale ErrorHandler, HTTP-Fehler und Fehler aus resource.Architektur der Anwendung
  • 08Modernes Routing & Lazy LoadingloadComponent, Child Routes, Parameter als Inputs und Parameter-Vererbung (neuer Standard in v22).Routing
  • 08Guards & ResolvercanActivate, canMatch, canDeactivate und resolve als Funktionen, mit Redirects über UrlTree / RedirectCommand.Routing
  • 09Signal Forms — die GrundlagenFormulare auf Basis von Signals: form(), [formField], schemabasierte Validierung. Stabil seit v22.Formulare
  • 09Signal Forms — fortgeschrittenEigene und feldübergreifende Validierung, async/HTTP mit Debounce, dynamische Listen, disabled/hidden, wiederverwendbare Schemas.Formulare
  • 09Typisierte Reactive FormsTypisierte FormGroup, nonNullable, FormArray — und wie sie neben Signal Forms bestehen.Formulare
  • 10HttpClient, Interceptors & die API-SchichtHttpClient ohne Setup (v21), Fetch als Standard (v22), funktionale Interceptors und eine API-Schicht mit DTO-Mapping.HTTP & Daten
  • 11Performance — eine ChecklisteWas wirklich zählt: OnPush + Signals, track, @defer, Lazy Routes, Bilder, Bundles und Hydration.Qualität & Performance
  • 11Testen mit VitestVitest ist ab v21 der Standard-Runner. TestBed, Komponenten mit Signals, Services, HTTP und Harnesses.Qualität & Performance
  • 11DevTools: ein Blick in deine AppChrome DevTools und Angular DevTools Schritt für Schritt: was jedes Panel zeigt, mit Übungen, die auf dieser Website funktionieren.Qualität & Performance
  • 11Angular AriaHeadless, barrierefreie Komponenten, die du nach Belieben stylst: Tabs, Accordion, Menü, Listbox...Qualität & Performance
  • 12SSR, Prerendering & Render ModesWann auf dem Server gerendert wird, wann beim Build und wann nur im Browser: RenderMode pro Route, app.routes.server.ts und die echten Kosten.SSR & Hydration
  • 12Hydration: Event Replay & inkrementellWie der Browser das Server-HTML übernimmt, ohne es neu zu rendern: vollständige Hydration, Event Replay, @defer (hydrate on …) und Mismatch-Fehler.SSR & Hydration
  • 12Servertauglicher Codewindow gibt es auf dem Server nicht: PLATFORM_ID, afterNextRender, DOCUMENT, TransferState, der HTTP-Cache und PendingTasks.SSR & Hydration
  • 13Micro FrontendsWann es sich lohnt, eine App in mehrere Deployments aufzuteilen und wann es nur Komplexität ist: Native Federation, Module Federation, iframes, Web Components.Architektur im großen Maßstab
  • 13Web Components mit Angular ElementscreateCustomElement: eine Angular-Komponente, die überall als HTML-Tag nutzbar ist, plus Kommunikation zwischen Apps über Events und BroadcastChannel.Architektur im großen Maßstab
  • 13Monorepo, Bibliotheken & GrenzenEin Workspace mit mehreren Apps und Bibliotheken: ng generate library, Nx, Secondary Entry Points, Publishing und Abhängigkeitsregeln.Architektur im großen Maßstab
  • 13Hexagonale Architektur (Ports & Adapters)Eine Domäne ohne Angular, Ports als abstrakte Klassen und austauschbare Adapter: HTTP, localStorage oder In-Memory, ohne die UI anzufassen.Architektur im großen Maßstab
  • 14Signals unter der HaubeDer reaktive Graph: Producer und Consumer, Push-Pull, warum computed lazy und glitch-frei ist, das Diamond-Problem und wann Effects laufen.Fortgeschrittene Reaktivität
  • 14Fortgeschrittenes RxJS: Operatoren höherer OrdnungswitchMap, mergeMap, concatMap und exhaustMap an einem Live-Diagramm, dazu Retry mit Backoff, shareReplay und wie du Leaks vermeidest.Fortgeschrittene Reaktivität
  • 14NgRx SignalStoreState Management im großen Stil auf Signals: withState, withComputed, withMethods, rxMethod, Entities und Hooks — und wann ein einfacher Store reicht.Fortgeschrittene Reaktivität
  • 14Zustandsautomaten mit SignalsKeine unmöglichen Zustände mehr: ein typisierter Automat auf Signals, mit Übergängen, Guards und einem Live-Zustandsdiagramm.Fortgeschrittene Reaktivität
  • 15WebSockets: Echtzeit, die das Netz überstehtEin typisierter Socket als Signals: Reconnect mit Backoff und Jitter, Heartbeat, ein Postausgang im Offline-Modus und SSR-Sicherheit.Echtzeit & offline
  • 15Optimistische Updates & RollbackEine sofortige UI, die ehrlich bleibt: Snapshot und Rollback, Antworten in falscher Reihenfolge, Idempotenzschlüssel.Echtzeit & offline
  • 15Offline-first: Service Worker & CachingDer Angular Service Worker: Caching-Strategien, Update-Hinweise und ein Postausgang für Änderungen, die offline entstehen.Echtzeit & offline
  • 16Web WorkersSchwere Berechnungen, ohne die UI zu blockieren: ein echter Worker in der Seite, Message Passing und wann es sich nicht lohnt.Fortgeschrittene UI & Performance
  • 16Riesige Listen: Virtual Scroll100.000 Zeilen ohne Ruckeln: CDK Virtual Scroll, itemSize, trackBy und die Alternativen (Paginierung, Infinite Scroll).Fortgeschrittene UI & Performance
  • 16Moderne Animationen & View Transitionsanimate.enter und animate.leave statt des veralteten @angular/animations, CSS-Transitions und View Transitions bei der Navigation.Fortgeschrittene UI & Performance
  • 16Memory Leaks finden und behebenSubscriptions, Timer und Listener, die ihre Komponente überleben – wie du sie in den DevTools findest und schließt.Fortgeschrittene UI & Performance
  • 17Sicherheit: XSS, Sanitizing & CSPWas Angular automatisch gegen XSS tut, wann bypassSecurityTrust gefährlich wird, dazu CSP mit Nonce, Trusted Types und XSRF.Sicherheit & Globalisierung
  • 17Authentifizierung: Tokens & RefreshEin Interceptor, der das Token anhängt, bei 401 einmal erneuert und die wartenden Requests wiederholt — dazu, wo du das Token aufbewahrst.Sicherheit & Globalisierung
  • 17OAuth 2.1 & OpenID Connect mit PKCEDer Authorization-Code-Flow mit PKCE Schritt für Schritt: Verifier und Challenge, State und Nonce, das ID-Token und warum ein BFF sicherer ist.Sicherheit & Globalisierung
  • 17Internationalisierung (i18n)$localize, das i18n-Attribut, ICU plural/select, ein Build pro Sprache vs. Übersetzungen zur Laufzeit und Formatierung nach Locale.Sicherheit & Globalisierung